I want to get information by user id, so lets add this to the model:
public function getById ($id)
{
$sql = 'SELECT * FROM users';
return ActualDbHander::run($sql);
}
later, I want to get only some fields:
public function getById ($id, $fields = '*')
{
$sql = 'SELECT '.$fields.' FROM users';
return ActualDbHander::run($sql);
}
another idea, lets add ordering:
public function getById ($id, $fields = '*', $orderBy = '')
{
$sql = 'SELECT '.$fields.' FROM users';
if ($orderBy != '')
{
$sql.= ' ORDER BY '.$orderBy;
}
return ActualDbHander::run($sql);
}
and I see this becaming messy and messy. What if I want to add JOIN-s? What if I want to add detailed WHERE-s? This is when "too generalic" methods born.
I completely agree with mch and Mjh comments, but, only in the case you actually want to have a "BD driver" (and build it yourself) I'd use different names for each query, very specific names, because you need to know exactly what a function will return to you.
So if I were you I would use names like getAllUsers, getUserById, getAllUsersOnlyPersonalData, getUserByIdOnlyPersonalData, getAllUsersOnlyContactData and so on (with fixed fields and filters for each method).
Note that in your examples you are not using at all the $id variable, so you are always receiving a list of users.
Regarding the method to make the queries, there are lots of ways to do it. Personally, I prefer MySQLi Object-Oriented prepared statements, because it's safe, easy and currently very extended, so I will use it just to ilustrate the examples.
Your functions would be something like this:
<?php
class DBDriver{
function openConnection(){
// If you don't always use same credentials, pass them by params
$servername = "localhost";
$username = "username";
$password = "password";
$database = "database";
// Create connection
$conn = new mysqli($servername, $username, $password, $database);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// Return conection object
return $conn;
}
function closeConnection($conn){
$conn->close();
}
function getAllUsers (){ // We don't need ids here
$conn = $this->openConnection();
// Array of arrays to store the results
// You can use any other method you want to return them
$resultsArray = [];
$sqlQuery = "SELECT * FROM users";
// In this case it's not neccesary to use prepared statements because we aren't binding any param but we'll use it to unify the method
if ($stmt = $conn->prepare($sqlQuery)) {
// Execute query
$stmt->execute();
// Bind result variables (I don't know your actuall column names)
$stmt->bind_result($id, $name, $email, $phone, $birthdate);
// Fetch values
while ($stmt->fetch()) {
$resultsArray[] = [$id, $name, $email, $phone, $birthdate];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
// If no results, it returns an empty array
return $resultsArray;
}
function getUserByIdOnlyContactData ($userId){
$conn = $this->openConnection();
// Array to store the results (only one row in this case)
$resultsArray = [];
$sqlQuery = "SELECT name, email, phone FROM users WHERE id = ?";
if ($stmt = $conn->prepare($sqlQuery)) {
// Bind parameter $userId to "?" marker in $sqlQuery
$stmt->bind_param("i", $userId);
$stmt->execute();
$stmt->bind_result($name, $email, $phone);
// If id found
if ($stmt->fetch()) {
$resultsArray = [$name, $email, $phone];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
return $resultsArray;
}
function getAllUserOnlyBirthdayDataOrderByBirthday (){
$conn = $this->openConnection();
$resultsArray = [];
$sqlQuery = "SELECT id, name, birthdate FROM users ORDER BY birthdate";
if ($stmt = $conn->prepare($sqlQuery)) {
$stmt->execute();
$stmt->bind_result($id, $name, $birthdate);
while ($stmt->fetch()) {
$resultsArray[] = [$id, $name, $birthdate];
}
// Close statement
$stmt->close();
}
$this->closeConnection($conn);
return $resultsArray;
}
} // Class end
This way it's true you will have lots of functions depending on your requirements but as you can see it's extremely easy to add new ones or modify them (and you won't get mad with many different options in the same function).
Hope this helps you to organize your database driver!
Related
I tried the questions with similar titles, but they are all regular queries that are not in functions.
I am trying to create an update function in a Database class so I don't have to write out the entire process over and over. However, I am getting the error:
Invalid parameter number: number of bound variables does not match number of tokens
Here is my function.
public function updateRow($query, $params) {
try {
$stmt = $this->master_db_data->prepare($query);
foreach($params as $key => $val) {
$stmt->bindValue($key+1, $val);
$stmt->execute();
return true;
}
} catch(PDOException $e) {
die("Error: " . $e->getMessage());
}
}
And its usage:
$query = "UPDATE records SET content=?, ttl=?, prio=?, change_date=? WHERE id=?";
$params = array($SOA_content, $fields['SOA_TTL'], '1', $DATE_TIME, $id);
if($db->updateRow($query, $params)) {
echo "Success";
}
else {
echo "Fail";
}
Doing it without a function works:
$pdo = new PDO("mysql:host={$host};dbname={$dbname};charset=utf8", $username, $password, $options);
$query = "UPDATE records SET content=:content, ttl=:ttl, prio=:prio, change_date=:change_date WHERE id=:id";
$stmt = $pdo->prepare($query);
$stmt->bindValue(":content", $SOA_content);
$stmt->bindValue(":ttl", $fields['SOA_TTL']);
$stmt->bindValue(":prio", 1);
$stmt->bindValue(":change_date", $DATE_TIME);
$stmt->bindValue(":id", $id);
$stmt->execute();
Am I wrong with my bindValue in the function? If so, how?
Always make sure your execute calls happen after all binding has been performed. In this situation, move the execute out of the binding loop.
Ok, so I am having a lot of trouble with Prepared statements. I've done hours of research and still can't seem to fully understand everything...
I really feel like I need to understand Prepared statements because I was just about to release a few new free APIs on my website (which require API Key to execute API) but I recently realized how insecure everything is.... I can simply use SQL injection to bypass API Key check, e.g. 'OR'1'='1
Here is how I validate API Key:
$apikey = $_GET['key'];
$sql = "SELECT * FROM `table` WHERE `key` = '$apikey'";
$query = mysqli_query($con, $sql);
if($query)
{
$fetchrow = mysqli_fetch_row($query);
if(isset($fetchrow[0]))
{
echo "API Key is valid!";
}
else
{
echo "API KEY is invalid";
}
}
And like mentioned above this can easily be bypassed by executing my API like this
http://website.com/api.php?key='OR'1'='1
This really scared me at first, but then I did some research and learned a good way to prevent any form of SQL injection is to use prepared statement, so I did a lot of research and it just seems quite complicated to me :/
So I guess my question is, how can I take my above code, and make it function the same way using prepared statements?
Probably everything you need:
class Database {
private static $mysqli;
Connect to the DB:
public static function connect(){
if (isset(self::$mysqli)){
return self::$mysqli;
}
self::$mysqli = new mysqli("DB_HOST", "DB_USER", "DB_PASS", "DB_NAME");
if (mysqli_connect_errno()) {
/*Log error here, return 500 code (db connection error) or something... Details in $mysqli->error*/
}
self::$mysqli->query("SET NAMES utf8");
return self::$mysqli;
}
Execute statement and get results:
public static function execute($stmt){
$stmt->execute();
if ($mysqli->error) {
/*Log it or throw 500 code (sql error)*/
}
return self::getResults($stmt);
}
Bind results to the pure array:
private static function getResults($stmt){
$stmt->store_result();
$meta = $stmt->result_metadata();
if (is_object($meta)){
$variables = array();
$data = array();
while($field = $meta->fetch_field()) {
$variables[] = &$data[$field->name];
}
call_user_func_array(array($stmt, "bind_result"), $variables);
$i = 0;
while($stmt->fetch()) {
$array[$i] = array();
foreach($data as $k=>$v)
$array[$i][$k] = $v;
$i++;
}
$stmt->close();
return $array;
} else {
return $meta;
}
}
Class end :)
}
Example of usage:
public function getSomething($something, $somethingOther){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE something = ? AND somethingOther = ?");
$stmt->bind_param("si", $something, $somethingOther); // s means string, i means number
$resultsArray = Database::execute($stmt);
$someData = $resultsArray[0]["someColumn"];
}
Resolving your problem:
public function isKeyValid($key){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE key = ? LIMIT 1");
$stmt->bind_param("s", $key);
$results = Database::execute($stmt);
return count($results > 0);
}
PHP automatically closes DB connection so no worries about it.
$sql = "SELECT * FROM `table` WHERE `key` = ?";
if(stmt = $mysqli->prepare($sql)) {
$stmt->bind_param("i", $apikey);
$stmt->execute();
$stmt->bind_result($res);
$stmt->fetch();
$stmt->close();
}
See more - http://php.net/manual/en/mysqli.prepare.php
Iam trying to make a OOP based forum in PHP and currently im stuck at making the Database class. Specifically Iam stuck at making a "general purpose" insert class function for the Datatable class (using PDO btw).
class DB
{
private $dbconn;
public function __construct(){
}
protected function connect($dbname, $dbhost='127.0.0.1', $dbuser='root', $dbpass=''){
try{
$this->dbconn = new PDO("mysql:host=$dbhost;dbname=$dbname;", $dbuser, $dbpass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));
}
catch(PDOException $e){
echo 'Connection failed: '.$e->getMessage()."<br />";
}
}
protected function disconnect(){
$this->dbconn = null;
}
public function insert($dbname, ){
$this->connect($dbname);
try{
# prepare
$sql = "INSERT INTO pdodemotable (firstname, lastname, age, reg_date)
VALUES (?, ?, ?, now())";
$stmt = $dbconn->prepare($sql);
# the data we want to insert
$data = array($firstname, $lastname, $age);
# execute width array-parameter
$stmt->execute($data);
echo "New record created successfully";
}
catch(PDOException $e){
echo $sql . "<br>" . $e->getMessage();
}
}
}
The insert function is as you see unfinished. I cant figure out how to get the insert function to adapt to ANY amount of arguments, ANY amount of database columns and ANY table. The code thats in the function right now is taken from one of my other projects where I used procedural programming. Its by first time using OOP with Databases.
Im a newbie to both OOP and PDO. There must be some sort of method or function that could help me that Im missing. The only solution I see right now is to use a ridicoulus amount of string handling and if statement... it cant be the best solution... there must be a easier way...
First notice - you don't need the $dbname parameter for insert method, instead it should be a constructor parameter:
class DB {
private $dbconn;
public function __construct($dbname, $dbhost='127.0.0.1', $dbuser='root', $dbpass='') {
// also don't catch the error here, let it propagate, you will clearly see
// what happend from the original exception message
$this->dbconn = new PDO("mysql:host=$dbhost;dbname=$dbname;", $dbuser, $dbpass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"));
}
...
}
As for the insert method - first try to imagine how it will be used.
For example, it can be like this:
$db = new DB('mydb');
$db->insert('mytable', array('firstname'=>'Pete', 'lastname'=>'Smith'));
Then you can pass the table name and data (keys/values) into the method:
public function insert($table, $data) {
// again, no need to try / catch here, let the exceptions
// do their job
// handle errors only in the case you are going to fix them
// and not just to ingnore them and 'echo', this can lead to much worse problems
// see the explanation below regarding the `backtick` method
$table = $this->backtick($table);
$fields = array();
$placeholders = array();
$values = array();
foreach($data as $key=>$value) {
$fields[] = $this->backtick($key);
// you can also process some special values like 'now()' here
$placeholders[] = '?';
}
$fields = implode($fields, ','); // firstname, lastname
$placeholders = implode($placeholders, ','); // ?, ?
$sql = "INSERT INTO $table ($fields) values ($placeholders)";
$stmt = $this->dbconn->prepare($sql);
$stmt->execute(array_values($data));
}
public function update($table, $id, $data) {
$table = $this->backtick($table);
$fields = array();
foreach($data as $key=>$value) {
$fields[] = $this->backtick($key) . " = ?";
}
$fields = implode($fields, ','); // firstname=?, lastname=?
$sql = "UPDATE $table SET $fields where id=?";
$stmt = $this->dbconn->prepare($sql);
$data['id'] = $id;
$stmt->execute(array_values($data));
if ($stmt->execute(array_values($data)) === false) {
print 'Error: ' . json_encode($stmt->errorInfo()). PHP_EOL;
}
while ($row = $stmt->fetchAll()) {
print json_encode($row) . PHP_EOL;
}
}
private function backtick($key) {
return "`".str_replace("`","``",$key)."`";
}
Another alternative is to create the separate object which will represent one table row (the ActiveRecord pattern).
The code which uses such object could look like this:
$person = new Person($db);
$person->firstName = 'Pete';
$person->lastName = 'Smith';
$person->save(); // insert or update the table row
Update on possible SQL injection vulnerability
I also added the update and backtick methods to illustrate the possible SQL injection.
Without the backtick, it is possible that update will be called with something like this:
$db->updateUnsafe('users', 2, array(
"name=(SELECT'bad guy')WHERE`id`=1#"=>'',
'name'=>'user2', 'password'=>'text'));
Which will lead to the SQL statement like this:
UPDATE users SET name=(SELECT'bad guy')WHERE`id`=1# = ?,name = ?,password = ? where id=?
So instead of updating the data for user with id 2, we it will change the name for the user with id 1.
Due to backtick method, the statement above will fail with Unknown column 'name=(SELECT'bad guy')WHEREid=2#' in 'field list'.
Here is the full code of my test.
Anyway, this probably will not protect you from any possible SQL injection, so the it is much better not to use the user input for known parameters like table name and field names.
Instead of doing something like $db->insert('mytable', $_POST), do $db->insert('mytable', array('first'=>$_POST['first'])).
Try to pass the arguments has an array, then, inside the method insert, do a foreach.
Something like:
$data['first_name'] = 'your name';
...
$data['twentieth_name'] = 'twentieth name';
foreach( $data as $key => $value )
$final_array[':'.$key] = $value;
$stmt->execute( $final_array );
Scenario:
I have a SQL Query INSERT INTO dbo.Grades (Name, Capacity, SpringPressure) VALUES ('{PHP}',{PHP}, {PHP})
The data types are correct.
I need to now get the latest IDENTIY which is GradeID.
I have tried the following after consulting MSDN and StackOverflow:
SELECT SCOPE_IDENTITY() which works in SQL Management Studio but does not in my php code. (Which is at the bottom), I have also tried to add GO in between the two 'parts' - if I can call them that - but still to no avail.
The next thing I tried, SELECT ##IDENTITY Still to no avail.
Lastly, I tried PDO::lastInsertId() which did not seem to work.
What I need it for is mapping a temporary ID I assign to the object to a new permanent ID I get back from the database to refer to when I insert an object that is depended on that newly inserted object.
Expected Results:
Just to return the newly inserted row's IDENTITY.
Current Results:
It returns it but is NULL.
[Object]
0: Object
ID: null
This piece pasted above is the result from print json_encode($newID); as shown below.
Notes,
This piece of code is running in a file called save_grades.php which is called from a ajax call. The call is working, it is just not working as expected.
As always, I am always willing to learn, please feel free to give advice and or criticize my thinking. Thanks
Code:
for ($i=0; $i < sizeof($grades); $i++) {
$grade = $grades[$i];
$oldID = $grade->GradeID;
$query = "INSERT INTO dbo.Grades (Name, Capacity, SpringPressure) VALUES ('" . $grade->Name . "',". $grade->Capacity .", ".$grade->SpringPressure .")";
try {
$sqlObject->executeNonQuery($query);
$query = "SELECT SCOPE_IDENTITY() AS ID";
$newID = $sqlObject->executeQuery($query);
print json_encode($newID);
} catch(Exception $e) {
print json_encode($e);
}
$gradesDictionary[] = $oldID => $newID;
}
EDIT #1
Here is the code for my custom wrapper. (Working with getting the lastInsertId())
class MSSQLConnection
{
private $connection;
private $statement;
public function __construct(){
$connection = null;
$statement =null;
}
public function createConnection() {
$serverName = "localhost\MSSQL2014";
$database = "{Fill In}";
$userName = "{Fill In}";
$passWord = "{Fill In}";
try {
$this->connection = new PDO( "sqlsrv:server=$serverName;Database=$database", $userName, $passWord);
$this->connection->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
}
catch( PDOException $e ) {
die("Connection Failed, please contact system administrator.");
}
if ($this->connection == null) {
die("Connection Failed, please contact system administrator.");
}
}
public function executeQuery($queryString) {
$results = array();
$this->statement = $this->connection->query( $queryString );
while ( $row = $this->statement->fetch( PDO::FETCH_ASSOC ) ){
array_push($results, $row);
}
return $results;
}
public function executeNonQuery($queryString) {
$numRows = $this->connection->exec($queryString);
}
public function getLastInsertedID() {
return $this->connection->lastInsertId();
}
public function closeConnection() {
$this->connection = null;
$this->statement = null;
}
}
This is PDO right ? better drop these custom function wrapper...
$json = array();
for ($i=0; $i < sizeof($grades); $i++) {
//Query DB
$grade = $grades[$i];
$query = "INSERT INTO dbo.Grades (Name, Capacity, SpringPressure)
VALUES (?, ?, ?)";
$stmt = $conn->prepare($query);
$success = $stmt->execute(array($grade->Name,
$grade->Capacity,
$grade->SpringPressure));
//Get Ids
$newId = $conn->lastInsertId();
$oldId = $grade->GradeID;
//build JSON
if($success){
$json[] = array('success'=> True,
'oldId'=>$oldId, 'newId'=>$newId);
}else{
$json[] = array('success'=> False,
'oldId'=>$oldId);
}
}
print json_encode($json);
Try the query in this form
"Select max(GradeID) from dbo.Grades"
I am having a problem with mysqli. I am trying to search a database for all people who meet a category. While looping through the results, I want to create an instance of a "Person" class, passing the database connection to the class. This is where the problem starts. Here is my code.
$con = new mysqli($db_host,$db_user,$db_password,$db_name);
if (mysqli_connect_errno())
{
die(mysqli_connect_error()); //There was an error. Print it out and die
}
$sql = "SELECT id FROM users";
$stmt = $con->prepare( $sql );
if ($stmt)
{
$stmt->execute();
$stmt->bind_result($id);
while($stmt->fetch())
{
$person = new Person( $con );
}
$stmt->close();
}
If i move the $person = new Person( $con ); to just after the while loop, it successfully makes an object of the last person. It just won't work when inside the loop. What is the reason for this?
According to error shown, you can't use the same connection until previous result set is in use. In order to make it work, you can do something like this:
$personIDs = array();
while($stmt->fetch())
{
$personIDs[] = $id;
}
$stmt->close();
and than just go through all ids buffered into array:
foreach($personIDs as $id) {
$person = new Person( $con );
}
Or, you can use store_results
if ($stmt)
{
$stmt->execute();
$stmt->bind_result($id);
$stmt->store_results();
while($stmt->fetch())
{
$person = new Person( $con );
}
$stmt->free_result();
$stmt->close();
}