I'm slowly moving all of my LAMP websites from mysql_ functions to PDO functions and I've hit my first brick wall. I don't know how to loop through results with a parameter. I am fine with the following:
foreach ($database->query("SELECT * FROM widgets") as $results)
{
echo $results["widget_name"];
}
However if I want to do something like this:
foreach ($database->query("SELECT * FROM widgets WHERE something='something else'") as $results)
{
echo $results["widget_name"];
}
Obviously the 'something else' will be dynamic.
Here is an example for using PDO to connect to a DB, to tell it to throw Exceptions instead of php errors (will help with your debugging), and using parameterised statements instead of substituting dynamic values into the query yourself (highly recommended):
// connect to PDO
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "password");
// the following tells PDO we want it to throw Exceptions for every error.
// this is far more useful than the default mode of throwing php errors
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// prepare the statement. the placeholders allow PDO to handle substituting
// the values, which also prevents SQL injection
$stmt = $pdo->prepare("SELECT * FROM product WHERE productTypeId=:productTypeId AND brand=:brand");
// bind the parameters
$stmt->bindValue(":productTypeId", 6);
$stmt->bindValue(":brand", "Slurm");
// initialise an array for the results
$products = array();
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$products[] = $row;
}
According to the PHP documentation is says you should be able to to do the following:
$sql = "SELECT * FROM widgets WHERE something='something else'";
foreach ($database->query($sql) as $row) {
echo $row["widget_name"];
}
If you like the foreach syntax, you can use the following class:
// Wrap a PDOStatement to iterate through all result rows. Uses a
// local cache to allow rewinding.
class PDOStatementIterator implements Iterator
{
public
$stmt,
$cache,
$next;
public function __construct($stmt)
{
$this->cache = array();
$this->stmt = $stmt;
}
public function rewind()
{
reset($this->cache);
$this->next();
}
public function valid()
{
return (FALSE !== $this->next);
}
public function current()
{
return $this->next[1];
}
public function key()
{
return $this->next[0];
}
public function next()
{
// Try to get the next element in our data cache.
$this->next = each($this->cache);
// Past the end of the data cache
if (FALSE === $this->next)
{
// Fetch the next row of data
$row = $this->stmt->fetch(PDO::FETCH_ASSOC);
// Fetch successful
if ($row)
{
// Add row to data cache
$this->cache[] = $row;
}
$this->next = each($this->cache);
}
}
}
Then to use it:
foreach(new PDOStatementIterator($stmt) as $col => $val)
{
...
}
Related
I am working to create a content class which will pull all content from the database via a PDO, then run the results through a method which will convert the results into objects, and the loop through the objects, and echo the object to a specific module of the webpage.
What is expected
When the class is activated, there will be an instance created via a PDO that will retrieve the needed data from the mysql database. Then this information will echo out and display on the webpage.
What is happening
I end up with no errors and the boolean number "1" where content should be. At first I thought this was an sql error. I get the same result "1" if I use print_r($query_result). See my tests below.
My class code is below
<?php
class Content {
// --- START OF ACTIVE RECORD CODE ---
public $n_id;
public $n_name;
public $n_text;
public $n_photo;
// Instantiating a STATIC Database Connection for the class to use
static protected $dbconnect;
static public function database($dbconnect) {
self::$dbconnect = $dbconnect;
}
// constructing arguments
public function __construct($args=[]) {
$this->n_id = $args['n_id'] ?? '';
$this->n_name = $args['n_name'] ?? '';
$this->n_text = $args['n_text'] ?? '';
$this->n_photo = $args['n_photo'] ?? '';
}
// Multi use method to pass in sql that will execute the PDO only two parameters are bound ID and contentText.
static public function find_by_sql($sql) {
// -------------BEGIN PDO-----------
// preparing PDO by loading sql, calling db connection, and storing in variable $stmt
$stmt = self::$dbconnect->prepare($sql);
// Binding Parameters for sql
$stmt->bindParam(':nid', $n_id, PDO::PARAM_INT);
$stmt->bindParam(':nall', $n_name, PDO::PARAM_STR);
$stmt->bindParam(':ntext', $n_text, PDO::PARAM_STR);
$stmt->bindParam(':nphoto', $n_photo, PDO::PARAM_INT);
// executing $stmt PDO and storing the result in $stmt
$query_result = $stmt->execute();
return $query_result;
// clearing the PDO after information is stored in $record
$stmt->closeCursor();
// ------------END PDO ----------
// Checking to see if a result exist. If nop result is stored in $stmt, then it will echo "Database query failed."
if(!$query_result) {
exit("Query doesn't exist.");
}
// -------- BEGIN TURNING RESULTS INTO OBJECTS --------
$object_array = [];
// The result $stmt will be stored in the variable $record
while($record = $query_result->fetchAll()) {
// Taking $record and passing it to the static method instantiate() - see below. This method will return the $object_array.
$object_array[] = self::instantiate($record);
}
return $object_array;
// ------------ END TURNING RESULTS INTO OBJECTS --------
}
// method to test passing $sql to method find_all_sql();
static public function find_all(){
$sql = "SELECT * FROM nmain WHERE nid = :id AND nall = :nall AND ntext = :ntext AND nphoto = :nphoto";
return self::find_by_sql($sql);
}
// --- BEGIN INSTANTIATE METHOD TO CREATE OBJECTS ---
static protected function instantiate($record) {
$object = new self;
// Auto assign values
foreach($record as $property => $value) {
if(property_exists($object, $property)){
$object->$property = $value;
}
}
return $object;
}
// ----- END INSTANTIATE OF RECORD TO CREATE OBJECTS ---
// ---END OF ACTIVE RECORD CODE---
}
?>
**On my html webpage:**
$contents = Content::find_all();
foreach ((array) $contents as $content) {
echo $content;
}
What I have tested
This is the output I get when I run var_dump($stmt);
object(PDOStatement)#3 (1) { ["queryString"]=> string(119) "SELECT * FROM ndb WHERE id = :id AND nall = :nall AND ntext = :ntext AND nphoto = :nphoto" }
If I copy the query and paste it in myphpadmin the query will run binding the params.
This is the output if I run var_dump($query_result):
bool(true) if I use print_r($query_result) I get "1"
This passes my if(!$query_result) test
If I run var_dump($record) or var_dump($query_result) I get nothing. It seems as if $query_result, because it is a bool, has no array to pass to $record.Therefore, there is nothing to convert to an object.I am at a loss here. Is it my PDO binding?
Your fetch should be on the statement and not the result of the execute (which is just to say the execute has succeeded or failed), also fetchAll will attempt to return all records, what you most likely want is fetch to process 1 record at a time. So you should have something like...
while($record = $stmt->fetch()) {
You can now remove the earlier return which is stopping further processing.
Right now, I'd wrote a function in my model as:
public function getRowsByZipCode($zip)
{
// SQL to get all the rows with the given zip code
$stmt = $this -> getAdapter()
-> query( "SELECT *
FROM
table_name
WHERE
table_name.status = 1 AND
table_name.zip={$zip}");
$resultRows = $stmt->fetchAll();
// -------------------------------------------------------- //
// Convert result set to an array of objects
$resultObjects = array();
// If there is atleast one row found in DB
if(count($resultRows) > 0)
{
// Loop throguh all the rows in the resultset
foreach($resultRows as $resultRow) {
// Create table row and fill it with the details got from DB
$h = $this->createRow();
$h->setFromArray($resultRow);
// Add to the array
$resultObjects[] = $h;
}
}
return $resultObjects;
// -------------------------------------------------------- //
}
Which is working perfectly as I needed. And it is returning me an array that contains the tables row objects(App_Model_TableName Objects), which will be used later for further operations like save and delete etc.
What I really want is to remove the code that loop through the rows I got from the result set and converting each row to an object of App_Model_TableName that I'd wrote inside the comments // --- //.
Thanks in advance.
Firstly, I am assuming you are using PDO.
Try the following
class App_Model_TableName
{
public $status;
public $zip;
// public $other_column;
}
class YourClass
{
protected function getAdapter()
{
// Do adapter stuffs
}
public function query($query, array $param)
{
// When Using PDO always use prepare and execute when you pass in a variable
// This will help prevent SQL injection
$stmt = $this->getAdapter()->prepare($query);
return $query->execute($param);
}
/**
* #return App_Model_TableName[]
*/
public function getRowsByZipCode($zip)
{
// SQL to get all the rows with the given zip code
// This way will help prevent SQL injection
$query = "SELECT * FROM table_name WHERE table_name.status = 1 AND table_name.zip = :zip";
$qData = array(':zip' => $zip);
$results = $this->query($query, $qData);
return $results->fetchAll(PDO::FETCH_CLASS, 'App_Model_TableName');
}
}
Calling YourClass::getRowsByZipCode() will then return you an array of App_Model_TableName objects. You can then access them like:
$data = $instance_of_yourclass->getRowsByZipCode(12345);
foreach ($data as $row)
{
echo $row->zip;
echo $row->do_stuff();
}
All these awesome functions I found on:
http://php.net/manual/en/pdostatement.fetchall.php
http://php.net/manual/en/pdostatement.execute.php
Disclaimer: this code was not tested :(
Be cool but stay warm
Finally, I had found the solution:
public function getRowsByZipCode($zip)
{
// SQL to get all the rows with the given zip code
$stmt = $this -> getAdapter()
-> query( "SELECT *
FROM
table_name
WHERE
table_name.status = 1 AND
table_name.zip={$zip}");
$resultObjects= array();
while($data = $stmt->fetch())
{
$h = $this->createRow();
$h->setFromArray($data);
// Add to array
$resultObjects[] = $h;;
}
return $resultObjects;
}
I had removed the code that do the fetchAll() and loop through each row in the resultset. Now, I am taking each row from the resultset and creating an row of App_Model_TableName object using the data we got from the resultset.
Working perfectly for me.
I have two PHP scripts that I included below. Both of them attempt to do the same thing, but one works and one does not. I'm looking for someone to explain what PHP is doing under the covers. I'm new to PHP and I suspect that my Java experience is poisoning my thought process when I work in PHP.
What I'm attempting to do is functionally very simple -- Insert a question into a mySQL database table, retrieve the primary key of the inserted row, and then insert five answers into another table with a foreign key relationship to the question.
My original logic looked like this:
ManageQuestions.php:
<?php
session_start();
include('query.php');
echo "begin <br>";
if (isset($_POST['submit'])) {
echo "manageQuestion <br>";
$query = new Query;
$query->createTransaction();
$query->executeCreateUpdateDelete("INSERT INTO question (question) VALUES ('".$_POST['question']."'); ");
$question_pid = $query->getLastInsertedId();
$query->commitTransaction(); // Need to figure out how to do dirty reads so I can remove this.
echo $question_pid."<br>";
$result = $query->executeRead("SELECT question_pid FROM question where question_pid = '".$question_pid."';");
echo count($result)."<br>";
//if (count($result) === 1) {
$query->createTransaction(); // Need to figure out how to do dirty reads so I can remove this.
foreach($_POST['answer'] as $answer) {
$correctAnswers = 0;
$query->executeCreateUpdateDelete("INSERT INTO answer (question_fid, answer, isCorrect) VALUES ('".$question_pid."','".$answer['answer']."','".$answer['isCorrect']."')");
if ($answer['isCorrect'] === 1) {
$correctAnswers = $correctAnaswers + 1;
if ($correctAnswers > 1){
echo "Failed to insert answers";
$query->rollBackTransaction();
break;
}
}
}
echo "Success";
$query->commitTransaction();
/* } else {
echo "Failed to insert question";
$query->rollBackTransaction();
} */
}
?>
Query.php:
<?php
session_start();
class Query
{
private $host="<censored>";
private $username="<censored>";
private $password="<censored>";
private $db_name="<censored>";
private $pdo;
private $pdo_statement;
private $pdo_exception;
public function executeCreateUpdateDelete($pQuery)
{
$this->pdo_statement = $this->pdo->prepare($pQuery);
return $this->pdo_statement->execute();
}
public function executeRead($pQuery)
{
try
{
$dbh = new PDO("mysql:host=$this->host;dbname=$this->db_name", $this->username, $this->password);
$result = $dbh->query($pQuery);
$dbh = null;
return $result->fetchAll();
}
catch(PDOException $e)
{
echo $e->getMessage();
}
}
public function createTransaction()
{
$this->pdo = new PDO("mysql:host=$this->host;dbname=$this->db_name", $this->username, $this->password);
$this->pdo->beginTransaction();
}
public function commitTransaction()
{
$this->pdo->commit();
}
public function rollBackTransaction()
{
$this->pdo->rollBack();
}
public function getLastInsertedId()
{
$this->pdo->lastInsertId();
}
}
?>
When I rewrote my logic to not use a separate query class, I was able to do what I wanted to do. The only thing I've been able to find online about the life cycle of a PHP object is that it begins at the start of a script and ends at the end of a script. Does that imply that my query object is instantiated every time I call one of its methods and garbage collected when that particular method ends? Moving the logic out of that class and into the script caused my logic to work. This is what it looks like now:
ManageQuestions.php:
<?php
session_start();
include('query.php');
echo "Begin <br>";
if (isset($_POST['submit'])) {
echo "manageQuestion <br>";
$host="<censored>";
$username="<censored>";
$password="<censored>";
$db_name="<censored>";
$pdo = new PDO("mysql:host=$host;dbname=$db_name", $username, $password);
$stmt = $pdo->prepare("INSERT INTO question (question) VALUES ('".$_POST['question']."'); ");
$stmt->execute();
$question_pid = $pdo->lastInsertId();
echo $question_pid."<br>";
$stmt = $pdo->query("SELECT question_pid FROM question where question_pid = '".$question_pid."';");
$result = $stmt->fetchAll();
echo count($result)."<br>";
foreach($_POST['answer'] as $answer) {
$correctAnswers = 0;
$stmt = $pdo->prepare("INSERT INTO answer (question_fid, answer, isCorrect) VALUES ('".$question_pid."','".$answer['answer']."','".$answer['isCorrect']."')");
$stmt->execute();
}
echo "Success";
}
?>
Even though this fixed my issue, I don't understand why. If someone could explain that, I would be extremely grateful.
Cheers!
Does that imply that my query object is instantiated every time I call one of its methods and garbage collected when that particular method ends?
No. It's per request, not per method call. So the query object is instantiated every time the script is called and it gets unset (and not necessarily garbage collected) when the script ends.
However you could better manage the resource of the PDO object inside your Query class because you create a new instance (which would mean that it connects again to the database server which is not that cheap). So some lazy loading does not seem bad:
class Query
{
...
/** #var PDO */
private $pdo;
...
private function getPdo() {
if (!$this->pdo) {
$this->pdo = new PDO("mysql:host=$this->host;dbname=$this->db_name", $this->username, $this->password);
}
return $this->pdo;
}
public function executeRead($pQuery)
{
try {
$dbh = $this->getPdo();
$result = $dbh->query($pQuery);
return $result->fetchAll();
} catch (PDOException $e) {
echo $e->getMessage();
}
}
public function createTransaction()
{
$this->getPdo()->beginTransaction();
}
...
I am trying to retrieve a list of items from a mySQL db and insert them as a list in a select object on a webpage. The following is the bit of code that isnt working.
In the first line, I am trying to retrieve a JSON object from a public function called getBrands() in a singleton object I have created called DatabaseInterface.
The second line is then attempting to turn that JSON object into a php array.
Finally, I am running a loop which can option each item in between tags for the webpage.
Where am I going wrong?
<?php
var $brandArrayJSON = DatabaseInterface::getBrands();
$brandArray = JSON_decode($brandArrayJSON);
for ($loop=0; $loop < sizeof($brandArray); $loop++) {
echo "<option>$brandArray[$loop]</option>";
}
?>
EDIT: In case it helps, here is my DatabaseInterface singleton. I have included this file at the top of my php file
class databaseInterface {
private static $_instance;
// Private constructor prevents instantiation
private function __construct() {
}
public static function getInstance() {
if (!self::$_instance) {
self::$_instance = mysqli_connect(self::databaseHost, self::databaseUsername, self::databasePassword, self::databaseName);
if (mysqli_connect_errno(self::$_instance)) {
throw new Exception("Failed to connect to MySQL:" . mysqli_connect_error());
}
}
return self::$_instance;
}
public function getBrands() {
try {
$con = DatabaseInterface::getInstance();
} catch (Exception $e) {
// Handle exception
echo $e->getMessage();
}
$query = "SELECT psBrandName from brands";
$result = mysqli_query($con, $query) or die ("Couldn't execute query. ".mysqli_error($con));
$resultArray[] = array();
while ($row = mysqli_fetch_assoc($result)) {
extract($row);
$resultArray[] = $psBrandName;
}
return json_Encode($resultArray);
}
There is nothing "wrong" with the code, in that it should work (provided nothing is broken on the query-side). However, there are several things that should be improved.
First, basically what the getBrands() method is doing is equivalent to this:
$brandArray = json_encode(array('test','test2','test3'));
echo $brandArray; // returns ["test","test2","test3"]
Now, when you decode that you get the same thing you originally put in (an array):
$brandArray = json_decode('["test","test2","test3"]');
var_dump($brandArray); // Will dump an array
Since this is an array (not a PHP object), you can just use a foreach.
foreach($brandArray as $option) {
echo '<option>', $option, '</option>';
}
If you're worried about it being an object in some instances (maybe you had a non-array JS object which would be mostly the equivalent to a PHP associative array), you could cast the json_decode result into an array.
$brandArray = (array)$brandArray;
Now, in your getBrands() method, I would highly recommend just using $row['psBrandName'] instead of cluttering things up with extract, unless you have a really good reason to do this.
I am very new to PHP Object Orientated Programming so was wondering if I could get some good advice on a database object I created.
I call the class db and include my class into every page load and initiate the database object using $db = new db. I then call the method inside this for each action I may need (building a menu from the database, getting login information etc.) with different parameters depending on what it is i want to do.
It takes its first parameter as the query with the ? symbol as replacements for values I want to bind, the second parameter is the values to bind to it in an array that is then looped through inside the prepared_statement method and the third parameter is the type ( FETCH_ARRAY returns an array of a SELECT statements rows, NUM_ROWS returns the amount of rows affected and INSERT returns the last inserted ID).
An example of how I would call this function is below:
$db->prepared_execute( "SELECT * FROM whatever WHERE ? = ? ", array( 'password', 'letmein' ), NUM_ROWS );
The second and third parameters are optional for if there are no parameters to be bound or no return is needed.
As I am new to OOP I am finding it hard to get my head around exactly when to use correctly and what are public, private, static functions/variables and design patterns (Singleton etc.).
I've read many tutorials to get as far as I hav but I feel now I need to take it here to get further answers or advice on where to go next with OOP and with this class I've built.
It works as it is which for me is a good starting place except for any error handling which I will add in next but I want to make sure I am not making any obvious design errors here.
The code for the class is below:
class db {
var $pdo;
public function __construct() {
$this->pdo = new PDO('mysql:dbname=' . DB_NAME . ';host=' . DB_HOST . ';charset=utf8', DB_USER, DB_PASS);
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function prepared_execute( $query, $bind_values = null, $type = null ) {
$preparedStatement = $this->pdo->prepare( $query );
if( $bind_values ) {
$i = 1;
foreach( $bind_values as $bind_value ) {
$preparedStatement->bindValue($i, $bind_value);
$i++;
} }
$preparedStatement->execute();
if( $type == FETCH_ARRAY ) { return $preparedStatement->fetchAll(); }
elseif( $type == NUM_ROWS ) { return $preparedStatement->rowCount(); }
elseif( $type == INSERT ) { return $this->pdo->lastInsertId(); }
else{ return true; }
}
Your code is a bit outdated. You should use one of the visibility keywords instead of var to declare your properties. In this case you probably want to use protected so that it cant be modified from outside the class but so that any future sub classes can modify it internally. Youll also probably want to add a getter in-case you need to work with PDO directly (which you will - see my final statements below my class example).
Its bad to hard code you PDO connection information in the class. You should pass these in as parameters same as you would if using PDO directly. I would also add the ability to pass in a pre-configured PDO instance as well.
While not required, its a good idea to conform to PSR-0 through PSR-2; Sepcifically in your case im speaking about Class and method naming which should both be camelCase and the first char of the class should be capital. Related to this your code formatting is also ugly particularly your block statements... If thats jsut an issue with copy and paste then ignore that comment.
So overall i would refactor your code to look something like this:
class Db {
protected $pdo;
public function __construct($dsn, $user, $pass, $options = array()) {
if($dsn instanceof PDO) {
// support passing in a PDO instance directly
$this->pdo = $dsn;
} else {
if(is_array($dsn)) {
// array format
if(!empty($options)) {
$dsn['options'] = $options;
}
$dsn = $this->buildDsn($options);
} else {
// string DSN but we need to append connection string options
if(!empty($options)) {
$dsn = $this->buildDsn(array('dsn' => $dsn, 'options' => $options));
}
}
// otherwise just use the string dsn
// ans create PDO
$this->pdo = new PDO($dsn, $user, $pass);
}
// set PDO attributes
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function getConnection()
{
return $this->pdo;
}
protected function buildDsn($options) {
if($isDbParts = isset($options['dbname'], $options['hostname']) || !($isDsn = isset($option['dsn']))) {
throw new Exception('A dsn OR dbname and hostname are required');
}
if($isDsn === true) {
$dsn = $options['dsn'];
} else if {
$format = '%s:dbname=%s;host=%s';
$driver = isset($options['dbtype']) ? $options['dbtype'] : 'mysql';
$dsn = sprintf($format, $options['dbtype'], $options['dbname'], $options['host']);
}
if(isset($options['options'])) {
$opts = array();
foreach($options['options'] as $name => $value) {
$opts[] = $name . '=' . $value;
}
if(!empty($opts)) {
$dsn .= ';' . implode(';', $opts);
}
}
return $dsn;
}
public function preparedExecute( $query, $bind_values = null, $type = null ) {
$preparedStatement = $this->pdo->prepare( $query );
if( $bind_values ) {
$i = 1;
foreach( $bind_values as $bind_value ) {
$preparedStatement->bindValue($i, $bind_value);
$i++;
}
}
$preparedStatement->execute();
if( $type == FETCH_ARRAY ) {
return $preparedStatement->fetchAll();
}
elseif( $type == NUM_ROWS ) {
return $preparedStatement->rowCount();
}
elseif( $type == INSERT ) {
return $this->pdo->lastInsertId();
}
else {
return true;
}
}
}
Lastly, unless this is just for educational purposes I wouldnt do this. There are a ton of different queries that have varying part assemblies which arent considered here so at some point this is not going to support what you need it to do. Instead i would use Doctrine DBAL, Zend_Db or something similar that is going to support greater query complexity through its API. In short, dont reinvent the wheel.
I have developed something similar and it can helps you.
public function select($sql, $array = array(), $fetchMode = PDO::FETCH_ASSOC){
$stmt = $this->prepare($sql);
foreach ($array as $key => $value){
$stmt->bindValue("$key", $value);
}
$stmt->execute();
return $stmt->fetchAll();
}
public function insert($table, $data){
ksort($data);
$fieldNames = implode('`,`', array_keys($data));
$fieldValues = ':' .implode(', :', array_keys($data));
$sql = "INSERT INTO $table (`$fieldNames`) VALUES ($fieldValues)";
$stmt = $this->prepare($sql);
foreach ($data as $key => $value){
$stmt->bindValue(":$key", $value);
}
$stmt->execute();
}