I am trying to write my own MVC Framework using this tutorial. Everything worked fine and i completed the tutorial with no issues.
However later i decided to use PDO (PHP Data objects) instead of mysql() functions for Database Operations. So i modified My sqlQuery.php file to use PDO istead of mysql functions.
sqlQuery.php
<?php
class SQLQuery {
private $_dbHandle;
private $_result;
/**
* Connects to database
*
* #param $address
* #param $account
* #param $pwd
* #param $name
*/
function connect($address, $account, $pwd, $name) {
try{
$this->_dbHandle = new PDO("mysql:host=$address;dbname=$name", $account, $pwd);
}catch (PDOException $e) {
die($e->getCode() . " : " . $e->getMessage());
}
}
/** Disconnects from database **/
function disconnect() {
$this->_dbHandle = null;
}
function get($whereClause = array()) {
$query = "select * from $this->_table";
if(is_array($whereClause) && count($whereClause)){
$query .= " where ";
foreach($whereClause as $column=>$value)
$query .= " $column = $value";
}else if(is_int($whereClause)){
$query .= " where id = $whereClause ";
}
return $this->query($query);
}
/**
* Custom SQL Query
*
* #param $query
* #return array|bool
*/
function query($query) {
$this->_result = $this->_dbHandle->query($query);
$this->_result->setFetchMode(PDO::FETCH_CLASS, $this->_model);
if (preg_match("/select/i",$query)) {
$result = array();
$numOfFields = $this->_result->rowCount();
if($numOfFields > 1){
while($result[] = $this->_result->fetch()){
}
}else{
$result = $this->_result->fetch();
}
return $result;
}
return true;
}
}
Now when In my Controller when i print $this->Item->get() I get all the results in my database as Objects of Items Model and $this->Item->get(2) gives me the item object with id=2 as expected.
However, I don't like the idea that my API needs to be to call an additional method get() to get the objects of the Items Model, Instead it makes more sense that when a Items Model is initialized i get the desired object and so my API can be $this->item->mycolumnName.
To achieve this i tried to move the get() call in the Models constructor like this :
model.php
<?php
class Model extends SQLQuery{
protected $_model;
function __construct() {
$this->connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
$this->_model = get_class($this);
$this->_table = strtolower($this->_model)."s";
$this->get();
}
function __destruct() {
}
}
However this gives me a Fatal error
Fatal error: Maximum function nesting level of '256' reached, aborting! in /var/www/html/FitternityAssignment/library/sqlquery.php on line 59
I don't know what i have done wrong.
The issue is line 59: while($result[] = $this->_result->fetch()){
function query($query) {
$this->_result = $this->_dbHandle->query($query);
$this->_result->setFetchMode(PDO::FETCH_CLASS, $this->_model);
if (preg_match("/select/i",$query)) {
$result = array();
$numOfFields = $this->_result->rowCount();
if($numOfFields > 1){
while($result[] = $this->_result->fetch()){
}
}else{
$result = $this->_result->fetch();
}
return $result;
}
return true;
}
Let's narrow in on the issue:
while($result[] = $this->_result->fetch()){
}
Infinite loop.
When fetch() returns something, then it is added to $result, because $result is an array, and then of course it evaluates to true because the size of $result grows each time a fetched result is added.
$results = [];
while($row = $this->_result->fetch()){
$results[] = $row; // or whatever you need to do with the row
}
Might work. I say might because it depends on what $this->_result->fetch() returns when no results are left. I'll assume false or null, in which case the above will work, because when there are no more results then fetch() will return null or false and $row will then evaluate to false.
Related
I am new in PHP OOP and was wondering if someone could help me with this.
I have a basic class with one method which returns data from database. Currently I am calling the method which displays everything inside the function.
Here is my class Definition:
class Products{
//properties
public $familyName = "";
public $familyProduct = "";
//Methods
public function getFamily($catId){
global $conn;
$sql = "SELECT * FROM product_family WHERE catID = '$catId'";
$result = $conn->query($sql);
if($result->num_rows > 0){
while($row = $result->fetch_assoc()){
echo "<li>".$row['familyName']."</li>";
echo "<li>".$row['familyProduct']."</li>";
}
}
}
}
Here is how I call the method:
$Products = new Products;
$Products->getFamily( 4 );
This works however, how can I assign each data coming from database ( ex familyName, familyProduct ) into variables inside class implementation and then access them individually where ever I need to. Something like this:
$Products = new Products;
$Products->familyName;
$Products->familyProduct;
I have empty properties but I am not sure how can I assign values to them coming from the loop and then return them each.
Thanks,
There are view things I would change in your Code.
Don't make Properties public use use Getters and Setters.
This will protect you Object from being used the wrong way e.g. now you can't change the familyName from outside: $products->familyName = "some value" because this would make the data of the object corrupt.
global $conn; is a no go in OOP use the construct of the Object,
in your case $products = new Products($conn);
Now you can set a Cat ID $products->setCatId(4); and read the result
$familyName = $products->getFamilyName(); or $familyProduct = $products->getFamilyProduct();
If you have more than one result you will get an array, if catId will always result one row you can delete this part. If you learn more about OOP you will find out that the hole SQL stuff can be done with a separate Object, but this is off Topic.
class Products
{
// Properties
protected $conn;
protected $catId;
protected $familyName;
protected $familyProduct;
public function __construct($conn)
{
$this->conn = $conn;
}
// set Cat ID and get date
public function setCatId($catId)
{
$this->catId = (int) $catId;
$this->getDate();
}
public function getCatId()
{
return $this->catId;
}
// get Family Name
public function getFamilyName()
{
return $this->familyName;
}
// get Family Product
public function getFamilyProduct()
{
return $this->familyProduct;
}
// get date
protected function getDate()
{
$sql = "SELECT * FROM product_family WHERE catID = '$this->catId'";
$result = $this->conn->query($sql);
// Default if no result
$this->familyName = null;
$this->familyProduct = null;
// if one Result
if ($result->num_rows == 1)
{
$row = $result->fetch_assoc();
$this->familyName = $row['familyName'];
$this->familyProduct = $row['familyProduct'];
}
if ($result->num_rows > 1)
{
$this->familyName = [];
$this->familyProduct = [];
while ($row = $result->fetch_assoc())
{
$this->familyName[] = $row['familyName'];
$this->familyProduct[] = $row['familyProduct'];
}
}
}
}
I have made a class function which will get rows from a table in my database along with an optional argument. This works when getting a single row however I cant get this to work when multiple rows are returned.
Here is what is in the Users class
public function getUsers($filter="") {
$Database = new Database();
if($filter == 'male')
$extra = "WHERE gender = 'm'";
$sql = "SELECT *
FROM users
$extra";
if ($Database->query($sql))
return $Database->result->fetch_assoc();
else
return false;
}
Database Class
class Database {
private $db = array();
private $connection;
private $result;
public function __construct() {
$this->connect();
}
public function connect() {
$this->connection = mysqli_connect('mysql.com', 'username', 'pass');
mysqli_select_db($this->connection, 'database');
}
public function query($sql) {
$this->result = mysqli_query($this->connection, $sql);
return $this->result;
}
This is the code used to try and display the rows
if ($student = $User->getUsers($filter)) {
echo "<table>\n";
echo "<tr><td>Col 1</td><td>Col 2</td><td>Col 3</td><td>Col 4</td><td></td><td></td></tr>";
foreach($student as $row) {
echo "<tr>";
echo "<td>$row[col1]</td>";
echo "<td>$row[col2]</td>";
echo "<td>$row[col3]</td>";
echo "<td>$row[col4]</td>";
echo "<td>$row[col5]</td>";
echo "<td>$row[col6]</td>";
echo "</tr>\n";
}
echo "</table>";
}
(I'm learning OO PHP, bear with me)
I'm still learning OOP but I have used this:
protected $connection, $result, $_numRows;
public function query($sql)
{
$this->result = mysql_query($sql, $this->connection);
$this->_numRows = mysql_num_rows($this->result);
}
/*
* #desc get result af query
*
* #returns string $result
*/
public function getResult()
{
return $this->result;
}
/*
* #desc Return rows in table
* #returns int $_numRows
*/
public function numRows()
{
return $this->_numRows;
}
/*
* #desc Count rows and add to array
* #return string $rows array
*/
public function rows()
{
$rows =array();
for ($x=0; $x < $this->numRows(); $x++) {
$rows[] = mysql_fetch_assoc($this->result);
}
return $rows;
}
Then you could use something like this to get the rows:
public function getUsers($filter="") {
$Database = new Database();
if($filter == 'male')
$extra = "WHERE gender = 'm'";
$sql = "SELECT *
FROM users
$extra";
if ($this->numRows() == 0) {
echo "No rows found";
} else {
foreach ($this->rows() as $b) {
$c = array('something' => $b['col']);
}
return $c;
}
Modify the last part to suit your needs.
It's not clear in your code where $result comes from...
You should call the fetch_assoc() method of an object returned by:
$mysqli->query($query)
Why are you using $result->fetch_assoc() twice? you are returning an array.. you should deal with $student.
This question already has answers here:
Call to undefined method mysqli_stmt::get_result
(10 answers)
Closed 5 years ago.
On my server i get this error
Fatal error: Call to undefined method mysqli_stmt::get_result() in /var/www/virtual/fcb/htdocs/library/mysqlidbclass.php on line 144
And I am having a wrapper like this:
<?php
/* https://github.com/aaron-lord/mysqli */
class mysqlidb {
/**
* Set up the database connection
*/
public function __construct($server,$user,$password,$db){
$this->connection = $this->connect($server, $user, $password, $db, true);
}
/**
* Connect to the database, with or without a persistant connection
* #param String $host Mysql server hostname
* #param String $user Mysql username
* #param String $pass Mysql password
* #param String $db Database to use
* #param boolean $persistant Create a persistant connection
* #return Object Mysqli
*/
private function connect($host, $user, $pass, $db, $persistant = true){
$host = $persistant === true ? 'p:'.$host : $host;
$mysqli = new mysqli($host, $user, $pass, $db);
if($mysqli->connect_error)
throw new Exception('Connection Error: '.$mysqli->connect_error);
$mysqli->set_charset('utf8');
return $mysqli;
}
/**
* Execute an SQL statement for execution.
* #param String $sql An SQL query
* #return Object $this
*/
public function query($sql){
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
$stmt = $this->connection->query($sql);
# Affected rows has to go here for query :o
$this->affected_rows = $this->connection->affected_rows;
$this->stmt = $stmt;
return $this;
}
else {
throw new Exception;
}
}
/**
* Prepare an SQL statement
* #param String $sql An SQL query
* #return Object $this
*/
public function prepare($sql){
unset($this->stmt);
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
# Ready the stmt
$this->stmt = $this->connection->prepare($sql);
if (false===$this->stmt)
{
print('prepare failed: ' . htmlspecialchars($this->connection->error)."<br />");
}
return $this;
}
else {
throw new Exception();
}
}
public function multi_query(){ }
/**
* Escapes the arguments passed in and executes a prepared Query.
* #param Mixed $var The value to be bound to the first SQL ?
* #param Mixed $... Each subsequent value to be bound to ?
* #return Object $this
*/
public function execute(){
if(is_object($this->connection) && is_object($this->stmt)){
# Ready the params
if(count($args = func_get_args()) > 0){
$types = array();
$params = array();
foreach($args as $arg){
$types[] = is_int($arg) ? 'i' : (is_float($arg) ? 'd' : 's');
$params[] = $arg;
}
# Stick the types at the start of the params
array_unshift($params, implode($types));
# Call bind_param (avoiding the pass_by_reference crap)
call_user_func_array(
array($this->stmt, 'bind_param'),
$this->_pass_by_reference($params)
);
}
if($this->stmt->execute()){
# Affected rows to be run after execute for prepares
$this->affected_rows = $this->stmt->affected_rows;
return $this;
}
else {
throw new Exception($this->connection->error);
}
}
else {
throw new Exception;
}
}
/**
* Fetch all results as an array, the type of array depend on the $method passed through.
* #param string $method Optional perameter to indicate what type of array to return.'assoc' is the default and returns an accociative array, 'row' returns a numeric array and 'array' returns an array of both.
* #param boolean $close_stmt Optional perameter to indicate if the statement should be destroyed after execution.
* #return Array Array of database results
*/
public function results($method = 'assoc', $close_stmt = false){
if(is_object($this->stmt)){
$stmt_type = get_class($this->stmt);
# Grab the result prepare() & query()
switch($stmt_type){
case 'mysqli_stmt':
$result = $this->stmt->get_result();
$close_result = 'close';
break;
case 'mysqli_result':
$result = $this->stmt;
$close_result = 'free';
break;
default:
throw new Exception;
}
$this->num_rows = $result->num_rows;
# Set the results type
switch($method) {
case 'assoc':
$method = 'fetch_assoc';
break;
case 'row':
//return 'fetch_row';
$method = 'fetch_row';
break;
default:
$method = 'fetch_array';
break;
}
$results = array();
while($row = $result->$method()){
$results[] = $row;
}
$result->$close_result();
return $results;
}
else {
throw new Exception;
}
}
/**
* Turns off auto-committing database modifications, starting a new transaction.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function start_transaction(){
if(is_object($this->connection)){
return $this->connection->autocommit(false);
}
}
/**
* Commits the current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function commit(){
if(is_object($this->connection)){
# Commit!
if($this->connection->commit()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Rolls back current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function rollback(){
if(is_object($this->connection)){
# Commit!
if($this->connection->rollback()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Return the number of rows in statements result set.
* #return integer The number of rows
*/
public function num_rows(){
return $this->num_rows;
}
/**
* Gets the number of affected rows in a previous MySQL operation.
* #return integer The affected rows
*/
public function affected_rows(){
return $this->affected_rows;
}
/**
* Returns the auto generated id used in the last query.
* #return integer The last auto generated id
*/
public function insert_id(){
if(is_object($this->connection)){
return $this->connection->insert_id;
}
}
/**
* Fixes the call_user_func_array & bind_param pass by reference crap.
* #param array $arr The array to be referenced
* #return array A referenced array
*/
private function _pass_by_reference(&$arr){
$refs = array();
foreach($arr as $key => $value){
$refs[$key] = &$arr[$key];
}
return $refs;
}
}
?>
Is there any way to use another function so that I won't have to rewrite whole app? Please tell me if any.
Please read the user notes for this method:
http://php.net/manual/en/mysqli-stmt.get-result.php
It requires the mysqlnd driver. if it isn't installed on your webspace you will have to work with BIND_RESULT & FETCH
http://www.php.net/manual/en/mysqli-stmt.bind-result.php
http://www.php.net/manual/en/mysqli-stmt.fetch.php
Extracted from here
The reason for this error is that your server doesn't have the mysqlnd driver driver installed. (See here.) If you have admin privileges you could install it yourself, but there is also an easier way:
I have written two simple functions that give the same functionality as $stmt->get_result();, but they don't require the mysqlnd driver.
You simply replace
$result = $stmt->get_result(); with $fields = bindAll($stmt);
and
$row= $stmt->get_result(); with $row = fetchRowAssoc($stmt, $fields);.
(To get the numbers of returned rows you can use $stmt->num_rows.)
You just have to place these two functions I have written somewhere in your PHP Script. (for example right at the bottom)
function bindAll($stmt) {
$meta = $stmt->result_metadata();
$fields = array();
$fieldRefs = array();
while ($field = $meta->fetch_field())
{
$fields[$field->name] = "";
$fieldRefs[] = &$fields[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $fieldRefs);
$stmt->store_result();
//var_dump($fields);
return $fields;
}
function fetchRowAssoc($stmt, &$fields) {
if ($stmt->fetch()) {
return $fields;
}
return false;
}
How it works:
My code uses the $stmt->result_metadata(); function to figure out how many and which fields are returned and then automatically binds the fetched results to pre-created references. Works like a charm!
Also posted here.
I am trying to make an intermediate class which will log the queries in an array along with their execution time. Everything is fine and it works perfectly. But autocomplete doesnt work when i try to access the intermediate class. How can get the autocomplete to work. I am using Netbeans.
Intermediate classname is Model.
From my application, i have a class by the name Users which extends Model.
class Users extends Model
{
function __construct() {
parent::__construct();
$stmt = $this->prepare('SELECT * FROM users WHERE id=? ');
$stmt->bindValue(1, 1); //$stmt-> auto-complete is unavailable
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($rows); //i get results
}
}
My Model class looks like this.
class Model extends PDO
{
public static $log = array();
private $query_cache = array();
public function __construct() {
parent::__construct(
"mysql:dbname=".MYSQL_DB.";host=".MYSQL_HOST,
MYSQL_USER, MYSQL_PASSWORD
);
}
public function query($query) {
$time = "";
$query = mysql_real_escape_string(preg_replace( '/\s+/', ' ', $query ));
if (key_exists($query,$this->query_cache)
&& is_object($this->query_cache[$query]))
{
$result = $this->query_cache[$query];
} else {
$start = microtime(true);
$result = parent::query($query);
$time = microtime(true) - $start;
$this->query_cache[$query] = $result;
Logger::$logText['DATABASE'][] = array(
'QUERY' => $query,
'TIME' => number_format($time,4)
);
}
return $result;
}
/**
* #return LoggedPDOStatement
*/
public function prepare($query) {
return new LoggedPDOStatement(parent::prepare($query));
}
}
My LoggedPDOStatement looks like this.
class LoggedPDOStatement
{
/**
* The PDOStatement we decorate
*/
private $statement;
public function __construct(PDOStatement $statement) {
$this->statement = $statement;
}
/**
* When execute is called record the time it takes and
* then log the query
* #return PDO result set
*/
public function execute() {
$start = microtime(true);
$result = $this->statement->execute();
$time = microtime(true) - $start;
Model::$log[] = array(
'query' => '[PS] ' . $this->statement->queryString,
'time' => round($time * 1000, 3)
);
return $result;
}
/**
* Other than execute pass all other calls to the PDOStatement object
* #param string $function_name
* #param array $parameters arguments
*/
public function __call($function_name, $parameters) {
return call_user_func_array(
array($this->statement, $function_name), $parameters
);
}
}
Is their any better way of doing this ?
I have fixed this taking suggestions from #cillosis and #Touki
#Touki, i agree i shouldnt extend the PDO Class.
#cillosis, thanks for your comments.
This is the way i have written my class. I have not pasted the full code as its not complete yet. But i have checked it works. And i can log my queries as well. However i am not sure if i will be able to log the execution time.
class Model
{
/**
* The singleton instance
*
*/
static private $PDOInstance;
public function __construct($dsn="", $username = false, $password = false, $driver_options = false) {
if (!self::$PDOInstance) {
try {
self::$PDOInstance = new PDO(
"mysql:dbname=".MYSQL_DB.";host=".MYSQL_HOST,
MYSQL_USER, MYSQL_PASSWORD
);
} catch (PDOException $e) {
die("PDO CONNECTION ERROR: " . $e->getMessage() . "<br/>");
}
}
return self::$PDOInstance;
}
/**
* Initiates a transaction
*
* #return bool
*/
public function beginTransaction() {
return self::$PDOInstance->beginTransaction();
}
public function prepare($statement, $driver_options = false) {
//log the $statement
if (!$driver_options)
$driver_options = array();
return self::$PDOInstance->prepare($statement, $driver_options);
}
}
I can do this when selecting a single row fine but cant quite get my head around doing this for multiple rows of data.
For the single row I simply instantiate a new object that does a number of operations behind the scenes that bascially produces a row from the database as our object.
Example:
$object = new Classname($param);
foreach($object->row as $key=>$value) {
echo $key.":".$value."\n";
}
//output
id:1
firstname:steve
lastname:took
etc...
Any clever people here able to point me in the right direction please?
NOTE: just want to be able to create an object for each row rather than the one object with nested arrays
EDIT: sorry $object->row is a member of the class that stores selected row from the database
If I got you the answer is pretty simple mysql_fetch_object
Example:
while ($row = mysql_fetch_object($result)) {
echo $row->user_id;
echo $row->fullname;
}
Why don't you consider to use an ORM (Object Relational Mapper), like Doctrine or Propel?
I prefer Doctrine: http://www.doctrine-project.org/
Enjoy! :)
Here is an example
class users extends db {
public $users_id = 0;
public $users_group = 0;
public $users_firstname = 0;
public $users_lastname = 0;
public function open($id) {
if (is_numeric($id)) {
$sql = "SELECT * FROM users WHERE users_id = '$id'";
$res = parent::DB_SELECT($sql);
if (mysql_num_rows($res) <> 1) {
return 0;
}
} else {
$sql = "SELECT * FROM users " . $id;
$res = parent::DB_SELECT($sql);
if (mysql_num_rows($res) <= 0) {
return 0;
}
}
$data = array();
while ($row = mysql_fetch_array($res)) {
$this->users_id = $row['users_id'];
$this->users_group = $row['users_group'];
$this->users_firstname = $row['users_firstname'];
$this->users_lastname = $row['users_lastname'];
$data[] = (array) $this;
}
return $data;
}
public function setUsersId($users_id) { $this->users_id = addslashes($users_id); }
public function getUsersId() { return stripslashes($this->users_id); }
public function setUsersGroup($users_group) { $this->users_group = addslashes($users_group); }
public function getUsersGroup() { return stripslashes($this->users_group); }
public function setUsersFirstname($users_firstname) { $this->users_firstname = addslashes($users_firstname); }
public function getUsersFirstname() { return stripslashes($this->users_firstname); }
public function setUsersLastname($users_lastname) { $this->users_lastname = addslashes($users_lastname); }
public function getUsersLastname() { return stripslashes($this->users_lastname); }
}
You could use MySQLi.
// Connect
$db = new mysqli('host', 'user', 'password', 'db');
// Query
$users = $db->query('SELECT * from users');
// Loop
while($user = $users->fetch_object()) {
echo $users->field;
}
// Close
$users->close();
$db->close();
More info here: http://php.net/manual/en/book.mysqli.php