$db->select("users")->where(array("username", "=", "username"));
$db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
Ok, I want to write the statements like above, by chain the where() method onto select, update and delete.
My problem is; how to determine if I used the select, update or delete before the where, so I can bind the right values onto the right statement.
I want something like this:
public function where() {
if($this->select()) {
// so if $db->select("users")->where(array("username", "=", "username"));
// save the where data in the select variable.
}
elseif($this->update()) {
// so if $db->update("users", array("username" => "username", "password" => "12345"))->where(array("id", "=", "14"));
// save the where data in the update variable.
}
elseif($this->delete()) {
// so if $db->delete("users")->where(array("username", "=", "username"));
// save the where data in the delete variable.
}
}
But the code above is of course not valid, and I dont use any frameworks.
public function select($table, $what = null) {
$what == null ? $what = "*" : $what;
$this->_select = "SELECT {$what} FROM {$table}";
return $this;
}
You would have to maintain that state. It's not about telling whether the previous call was a select() or an update(), that's the wrong way to think about the problem. You just need each of select/update/delete to modify $this, so that $this, always knows what kind of query it's building.
A dead simple example:
public function select() {
$this->kind == 'select';
return $this;
}
public function where() {
if ($this->kind == 'select') {
...
return $this;
}
The only thing that your chained methods share is that they each return $this, so that a subsequent method can be chained onto the end. It's all about storing up state in $this until some final method call actually evalates the built-up query.
Something like:
public function select($table, $fields = '*')
{
$this->query = "SELECT {$fields} FROM `{$table}`";
return $this;
}
public function where($conditions = [])
{
if ($this->query)
{
if ($conditions)
{
foreach ($conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $conditions);
}
$db->query($this->query);
$this->query = '';
return $this;
}
}
This would work, however, you have to notice that this structure would allow you to do things like:
$db->where();
This is perfectly valid even though doesn't make sence to call where() in the database directly.
Also, queries that don't require a WHERE clause would not run, because only where() actually makes the call.
How to solve this?
We can actually use a very interesting mechanic of OOP: The destructor method. PHP destroys objects immediately after they are no longer in use, and we can explore this feature here as a trigger to run the query. We only have to separate the query to a new object.
class dbQuery
{
private $db;
private $conditions = [];
function where($conditions = [])
{
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
function __construct($db, $query)
{
$this->db = $db;
$this->query = $query;
}
function __destruct()
{
if ($this->conditions)
{
foreach ($this->conditions as $key => &$val)
$val = "`{$key}` = '{$val}'";
$this->query .= ' WHERE ' . implode(' AND ', $this->conditions);
}
$this->db->result = $db->query($this->query);
}
}
class Database
{
public $result = null;
protected static $instance;
function __construct()
{
if (!self::$instance)
self::$instance = new mysqli('localhost', 'user', 'password', 'dbname');
}
public function query($query)
{
return self::$instance->query($query);
}
public function select($table, $fields = '*')
{
return new dbQuery($this, "SELECT {$fields} FROM `{$table}`");
}
}
This way $db->where() won't work as it doesnt exist, and using $db->select('table') or $db->select('table')->where([...]) will both give results, and even allows extending the syntax to use where() multiple times like:
$db->select('table')->where(['id' => 100])->where(['price' => 1.99]);
Related
I'm wanting to create a new instance of my Class and assign it's attributes the values that are returned. The reason for this is I'm creating a series of methods inheriting from the calling class, as opposed to using static methods which I already had working.
Example of what I'm using currently:
public static function findById($id) {
$id = self::escapeParam($id);
$idVal = is_int($id) ? "i" : "s";
$sql = "SELECT * FROM ".static::$db_table." WHERE id = ? LIMIT 1";
return static::findByQuery($sql,$idVal,$id);
}
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$callingClass = get_called_class();
$object = new $callingClass;
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param($bindChar, $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$object = $result->fetch_object();
endif;
$statement->close();
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
What I tried was writing an instantiation method that creates a new instance of my class, and then assign each attribute of the object the value it returns from an array from a tutorial I did. However, the tutorial was fairly outdated and didn't use any new syntax or binding, so I was trying to rework this.
Example from the tutorial below:
public static function find_by_id($id) {
global $database;
$the_result_array = static::find_by_query("SELECT * FROM " . static::$db_table . " WHERE id = $id LIMIT 1");
return !empty($the_result_array) ? array_shift($the_result_array) : false;
}
public static function find_by_query($sql) {
global $database;
$result_set = $database->query($sql);
$the_object_array = array();
while($row = mysqli_fetch_array($result_set)) {
$the_object_array[] = static::instantation($row);
}
return $the_object_array;
}
public static function instantation($the_record){
$calling_class = get_called_class();
$the_object = new $calling_class;
foreach ($the_record as $the_attribute => $value) {
if($the_object->has_the_attribute($the_attribute)) {
$the_object->$the_attribute = $value;
}
}
return $the_object;
}
private function has_the_attribute($the_attribute) {
return property_exists($this, $the_attribute);
}
What I was trying to do from the tutorial, was to return my result as an array using a while, and then assigning a variable by passing the built array into the static::instantation() method, but it doesn't seem to ever be working correctly, as any public functions I create in my calling class (Admin for example) aren't called after as they don't exist due to the Class not being instantiated.
mysqli_result::fetch_object() accepts the class name as the first argument. You can pass the class name as an argument to that method and get the instance of the model. I am not sure why you have that much code but consider my example which I wrote based on your own code:
<?php
class Model
{
public static function findByQuery(string $sql, ?string $bindChar = null, ?string $bindVal = null): ?static
{
$statement = Database::$connection->prepare($sql);
if ($bindChar) :
$statement->bind_param($bindChar, $bindVal);
endif;
$statement->execute();
$result = $statement->get_result();
return $result->fetch_object(static::class);
}
}
class User extends Model
{
private $id;
}
class Database
{
public static mysqli $connection;
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Database::$connection = new mysqli('localhost', 'user', 'password', 'test');
$user = User::findByQuery('SELECT ? as id', 's', 'Dharman');
var_dump($user);
The output from that example is:
object(User)#4 (1) {
["id":"User":private]=>
string(7) "Dharman"
}
As you can see, the code created an instance of the class using late-static binding and it also assigned the value to a private property, which you can't do otherwise.
P.S. My example is a little bit tidier. I added parameter typing and removed a lot of unnecessary code. In particular, I remove empty try-catch which is a terrible practice.
I have now got this working, although I feel this is probably not the best way of doing it.
I'm primarily front end so please comment if there are improvements or best practices.
public static function findByQuery($sql,$bindChar = '',$bindVal = '') {
try {
$statement = Database::$connection->prepare($sql);
if(!empty($bindChar)) :
$statement->bind_param("$bindChar", $bindVal);
endif;
if($statement->execute()) :
$result = $statement->get_result();
$output = $result->fetch_object();
endif;
$statement->close();
if(!empty($output)) :
$class = get_called_class();
$object = new $class;
foreach(get_object_vars($output) as $key => $value) :
$object->$key = $value;
endforeach;
endif;
if(!empty($object)) :
return $object;
endif;
} catch(Exception $e) {
}
}
My initial thoughts were declaring an object and then I thought that the PHP fetch_object call would have just assigned my object it's properties after initiating the Class but that wasn't the case.
So what I've done is that if the statement is successful and a results object is created, I then get the object properties and values with the get_object_vars() command, and then loop through these as a key value pair, assigning each attribute it's returned value.
I can confirm this works as I can now run $admin->remove() from my removal script, as opposed to what I was having to do before which was Admin::remove($id);
I have a MySQL table with columns:
opID, opDateAdded, opLastUpdated, opUser, opPropertySaleID, opArray, refurbID, opRefurbCost, opViewingArranged, opOfferAccepted, opOfferAcceptedID, opPaon, opStreet, opPostcode, opPropertyType, opViewingDate, opViewingBy, opViewingPersonName, opFloorArea, opBedrooms, opBathrooms, opReceptions, opAskingPrice, opValuation, opOptMatchingBedrooms, opOptMatchingBuild, opOptMatchingType, opOptSimilarFloor, opOptDistance, opLatitude, opLongitude, opNotes
I want to have one function that allows me to update the columns of this table but sometimes only 3-4 columns will need updating, not always all of them.
I'm just wondering what the best method of approaching this is?
I could create a function like:
function updateOpportunity($opID, $opDateAdded, $opLastUpdated, $opUser, $opPropertySaleID, $opArray, $refurbID, $opRefurbCost, $opViewingArranged, $opOfferAccepted, $opOfferAcceptedID, $opPaon, $opStreet, $opPostcode, $opPropertyType, $opViewingDate, $opViewingBy, $opViewingPersonName, $opFloorArea, $opBedrooms, $opBathrooms, $opReceptions, $opAskingPrice, $opValuation, $opOptMatchingBedrooms, $opOptMatchingBuild, $opOptMatchingType, $opOptSimilarFloor, $opOptDistance, $opLatitude, $opLongitude, $opNotes) {
//update
}
And set them as optional then check if they're set or not, if they are then update those rows (using IFNULL in MySQL)
Or maybe it's better and tidier to create a property class and pass in a property:
function updateOpportunity($property) {
//update
}
I'm just wondering if there is a standard for creating a function to update a large set of columns that aren't always in need of updating at the same time.
you can create a generic function which works for all table and column.. like this.
function update($table, $data, $id)
{
$set= array();
foreach ($data as $key => $value)
{
$set[] = "{$key} ='".mysqli_real_escape_string($value)."'";
}
$sql = "UPDATE {$table} SET ".implode(', ', $set)." WHERE ID = '$id'";
mysqli_query($connection,$sql);
}
here is a function using a prepared statement.
function update($table, $data, $id)
{
$setPart = array();
$bindings = array();
foreach ($data as $key => $value)
{
$setPart[] = "{$key} = :{$key}";
$bindings[":{$key}"] = $value;
}
$bindings[":id"] = $id;
$sql = "UPDATE {$table} SET ".implode(', ', $setPart)." WHERE ID = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute($bindings);
}
Here the $connection is a mysqli_connection object which we need to create to execute any query to know about this click here.
and in second function $pdo is PDO connection object which we need to create to execute the query click here for more information.
You can read this link for more information.
to prevent SQL injection you can use mysqli_real_escape_string() function
click here for more information.
You could do that with a class.
class Table {
private $pdo;
private $table;
private $where;
private $key;
private $id;
private $values;
private $sql;
public function __construct($pdo, $table = null, $key = null, $where = array(), $id = null) {
$this->pdo = $pdo;
$this->table($table);
$this->values($values);
$this->key($key);
$this->id = $id;
}
public function table($table) {
$this->table = $table;
return $this;
}
public function where($where) {
$this->where = $where;
$this->id = null;
return $this;
}
public function key($key) {
$this->key = $key;
return $this;
}
public function id($id) {
$this->id = $id;
$this->where = array();
return $this;
}
private function resetValues() {
$this->values = array();
return $this;
}
private function getWhere() {
$where = '';
$comma = ' WHERE ';
foreach ($this->where as $key => $value) {
$where .= $comma . $key . '=?';
$values[] = $value;
$comma = ',';
}
if ($this->id) {
if (!$this->key) {
throw new \Exception('primary key required but not specified');
}
$where .= $comma . $key . '=?';
$values[] = $value;
$comma = ',';
}
return $where;
}
private function getWhat(&$values) {
$fields = '';
$comma = '';
foreach ($values as $key => $value) {
$fields .= $comma . $key . '=?';
$values[] = $value;
$comma = ',';
}
return $fields;
}
public function update(array $values) {
$this->sql = "UPDATE {$this->table} SET ";
. $this->resetValues()->getWhat()
. $this->getWhere();
return $this;
}
public function perform() {
// Check $this->sql exists ecc.
return $this->pdo->prepare($this->sql)->execute($this->values());
}
}
Then you could do:
$TUsers = new Table($pdo);
$TUsers->table('users')->key('user_id');
...
$TUsers->update(['name' => 'Leonardo'])->id(137)->perform();
Note that the class user does not know which DB interface the class is using - you could offload it via subclassing, by abstracting the connection interface (here the $pdo supplied to the constructor) and the actual execution (here perform()).
If needed you might want to cache the prepared statement by comparing the MD5 of the actual SQL (which contains no column values) against a LRU array of saved statements (I believe that PDO might already do that, though). If the statement isn't present in the cache then you prepare() it, otherwise you fetch it back from the cache:
private function getStatement() {
$key = md5($this->sql);
if (!array_key_exists($key, $this->stmtCache)) {
// Expunge the oldest or least used or less costly or...
// statement if cache is full - TODO
// ...
$this->stmtCache[$key] = [
'stmt' => $this->pdo->prepare($this->sql)
];
}
$this->stmtCache[$key]['ts'] = time();
return $this->stmtCache[$key]['stmt'];
}
The same class could be extended with methods such as INSERT, DELETE, SELECT etc., or ancillary functions such as ORDER, GROUP and so on. And it could run consistency checks on its data and/or UTF-8 clean your fields and/or log every query matching some specifications, and so on and so forth.
You could also look into Doctrine or Propel.
The Situation
I'm fairly new to object-oriented programming in PHP and currently I'm creating a small CMS for learning purposes. I've learned a lot about OOP on my way, but I'm facing a weird issue at the moment. I've created a Singleton class to deal with the database connection and queries.
public static function getInstance()
{
if(!isset(self::$instance))
{
self::$instance = new Database();
}
return self::$instance;
}
In the same class, there also is a method to execute queries. It takes two parameters, the query and an optional array with parameters to bind for the prepared statements. You can see its source below.
public function execute($query, $params = array())
{
$this->error = false; // Set 'error' to false at the start of each query.
if($this->query = $this->pdo->prepare($query))
{
if(!empty($params))
{
$index = 1;
foreach($params as $parameter)
{
$this->query->bindValue($index, $parameter);
++$index;
}
}
if($this->query->execute())
{
$this->results = $this->query->fetchAll(PDO::FETCH_OBJ);
$this->count = $this->query->rowCount();
}
else
{
$this->error = true;
}
}
return $this;
}
The Problem
If I have multiple queries on the same page, the results and count variables still contain the values of the first query. Imagine the following - the first query retrieves all users from my database. Let's say there are 15. The second query retrieves all blog posts from the database, let's say there are none. If no posts are present, I want to display a message, otherwise I run a loop to display all results. In this case, the loop is executed even though there are no blog posts, because the count variable is used to determine if there are posts in the database and it still holds the 15 from the first query somehow.
This obviously leads to some errors. Same with results. It still holds the value from the first query.
$query = Database::getInstance()->execute('SELECT * FROM articles ORDER BY article_id DESC');
if(!$query->countRes())
{
echo '<h2>There are no blog posts in the database.</h2>';
}
else
{
foreach($query->results() as $query)
{
echo '<article>
<h3>'.$query->article_heading.'</h3>
<p>'.$query->article_preview.'</p>
</article>';
}
}
The countRes() and results() methods simply return the variables from the DB class.
I hope that I have explained the problem understandable. Responses are very appreciated.
I would use a response object to avoid attaching query specific data to the global database object.
Example:
<?php
class PDO_Response {
protected $count;
protected $results;
protected $query;
protected $error;
protected $success = true;
public function __construct($query){
$this->query = $query;
try{
$this->query->execute();
}catch(PDOException $e){
$this->error = $e;
$this->success = false;
}
return $this;
}
public function getCount(){
if( is_null( $this->count ) ){
$this->count = $this->query->rowCount();
}
return $this->count;
}
public function getResults(){
if( is_null( $this->results ) ){
$this->results = $this->query->fetchAll(PDO::FETCH_OBJ);
}
return $this->results;
}
public function success(){
return $this->success;
}
public function getError(){
return $this->error;
}
}
Then in your database class:
public function execute($query, $params = array())
{
if($this -> _query = $this -> _pdo -> prepare($query))
{
if(!empty($params))
{
$index = 1;
foreach($params as $parameter)
{
$this -> _query -> bindValue($index, $parameter);
++$index;
}
}
return new PDO_Response($this->_query);
}
throw new Exception('Some error text here');
}
UPDATE: Moved execution into response class for error handling
Example usage (not tested)
$select = $database->execute('SELECT * FROM table');
if( $select->success() ){
//query succeeded - do what you want with the response
//SELECT
$results = $select->getResults();
}
$update = $database->execute('UPDATE table SET col = "value"');
if( $update->success() ){
//cool, the update worked
}
This will help fix your issue in the event that subsequent queries fail, there will not be any old query data attached to the database object.
I worked with procedural PHP for a long time but not to long ago I just started to learn OOP PHP. For better understanding I decided to create a class to manage my DB. As I started to learn from phpacademy my first select function was quite poor, so I just added some other arguments. I ended up with this:
public function get($tabel, $where = null, $columns = array('*'), $other = null){
if($where){ $where = $this->where($where);; }
$select = 'SELECT '.$this->select($columns);
return $this->action($select, $tabel, $where, $other);
}
// $db->get('users',array('group',1),array(*),array('LIMIT' => 10));
(action executes the query)
Then I decided to modify this to get better control.
public function getModified($table, $param = array()){
$select = (isset($param['S'])) ? $this->select($param['S']) : '*';
$where = (isset($param['W'])) ? $param['W'] : array();
$other = array();
if(isset($param['GB'])){ $other['GROUP BY'] = $param['GB']; }
if(isset($param['OB'])){ $other['ORDER BY'] = $param['OB']; }
if(isset($param['L'])){ $other['LIMIT'] = $param['L']; }
return $this->action('SELECT '.$select, $table, $where, $other);
}
// $db->getModified('users',array('WHERE' => array('id',1), 'LIMIT' => 10));
But today I found in FuelPHP's documentation this: DB::get()->from('users')->where('id', 1)->limit(10);
Because I do this class to practice OOP PHP I've tried to create something similar but to execute the query I had to add an other function, which I want to skip. Could you show me an example how this method should/could work?
And I know that it's objective but which one would you prefer?
I'll just explain how the FuelPHP way works. This is btw a pattern that is used in a lot of DB wrapper classes.
In short, your Database package consists of 2 classes. 1 that handles connection to your database, multiple connections,... This is the DB class. this will look something like this:
class DB
{
private static $connections = array();
public static function addConnection($name, $connection)
{
self::$connection[$name] = $connection;
}
public static function get($name='default')
{
return new QueryBuilder(self::$connection[$name]);
}
}
This class manages all connections and returns a queryBuilder instance if you need to query a connection. The QueryBuilder will look something like this:
class QueryBuilder
{
private $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
public function select()
{
$this->queryType = 'SELECT';
return $this;
}
public function from($table)
{
$this->table = $table;
return $this;
}
public function all()
{
return $this->connection->query(
$this->getSqlQuery()
);
}
public function getSqlQuery()
{
return $this->queryType . ' ' . $this->columns . ' FROM ' . $this->table;
}
}
so now you can use the clases above as:
DB::setConnection('default', $myPdoConnection);
DB::get()->select()->from('my_table')->all();
Note that the Querybuilder assumes that your $connection has a query method
A nice example from the Eloquent/laravel QueryBuilder: https://github.com/illuminate/database/blob/master/Query/Builder.php
What you are searching for is method chaining, also fuelphp (just like doctrine and others) is caching what you are sending in and builds the query after that:
public function from($table){
$this->_table = $table;
return $this; //this is the important part for chaining
}
public function where($key,$value){
$this->where = array($key => $value);
return $this;
}
public function andWhere($key,$value){
if(!$this->where) $this->where = array();
$this->where[$key] = $value;
return $this;
}
public function getQuery(){
//Build query here from what we stored before
$query = '';
....
return $query;
}
Oh well, what i forgot is what DB::get returns, an instance of what the class above is and executes that then:
public static function get(){
return new Query(); //above functions are part of Query class
}
I have a DB class that I've created several functions in to return various values. One of the functions returns (or is supposed to) a "user" class object that represents a logged in user for the application.
class user {
public $guid = '';
public $fname = '';
public $lname = '';
public function __construct() {}
public function print_option() {
return "<option value='$this->guid'>$this->name</option>";
}
}
In the DB class I have the following 2 functions:
public function get_user($uid) {
$sql = '';
$usr = new user();
$sql = "SELECT guid, fname, lname FROM ms.users WHERE guid=?";
if($sth = $this->conn->prepare($sql)) {
$sth->bind_param('s', $uid);
if($sth->execute()) {
$sth->bind_result($usr->guid, $usr->fname, $usr->lname);
$sth->fetch();
print_r($usr); // PRINTS OUT CORRECTLY
return $usr;
}
else {return null;}
}
else {return null;}
}
public function get_practice_user_list($pguid) {
$ret = '';
$sql = "SELECT user_guid FROM ms.perm WHERE p_guid=?";
if($sth = $this->conn->prepare($sql)) {
$sth->bind_param('s', $pguid);
if($sth->execute()) {
$usr = new user();
$guid = '';
$sth->bind_result($guid);
while($sth->fetch()) {
print_r($guid); // PRINTS GUID correctly
$usr = $this->get_user($guid);
print_r($usr); // PRINTS NOTHING object is null so prints "error" two lines later.
if($usr != null) $ret .= $usr->print_option();
else print "error";
}
return $ret;
}
else {return null;}
}
else {return null;}
}
I'm just not understanding why the "user" object is not returning in this instance. Others calls to the get_user function work just fine and return the user class object pertaining to that user.
TIA
I guess you guid may be an integer so
$sth->bind_param('s', $uid);
bind_param's first param should be 'i' not 's';
http://www.php.net/manual/en/mysqli-stmt.bind-param.php
The problem was with the query. Since the code was just looping through one query (get_practice_user_list), then calling the get_user function and attempting a second query MySQL came back with an error of out of sync message. When I looked that up, I was able to fix it by doing a fetch_all on the first query then looping through that array to get the users.