Use a closure for the PDO FETCH instructions - php

It occurs to me that I want to give my PDO result the instructions on how to instantiate itself into an object when it gets iterated later on, but I do not want to perform the logic until that time.
I've been looking for the existence of this PDO functionality but I cannot find it. What I want to do is essentially this:
public function getUsers()
{
$sql = 'select first_name, last_name, phone, address from users';
return $this->pdo->query($sql, PDO::FETCH_ASSOC, function($row) {
$user = new User();
$user->setName($row['first_name'] . ' ' .$row['last_name'])
->setPhoneNumber($row['phone'])
->setMailingAddress($row['address']);
return $user;
});
}
Is there a good way of accomplishing this with PHP and specifically PDO? Looping through the iterator is not an acceptable answer. I only want to iterate on this recordset once during execution.

You can use a Generator for this via yield. Only when the internal pointer is on certain iteration, the concrete object will be yielded.
class User {
public $picture;
public function __construct($pic) {
$this->picture = $pic;
}
}
function getUsers() {
$pdo = new PDO('mysql:host=localhost;dbname=users', 'root', '');
$query = "SELECT * FROM votesusers";
$res = $pdo->query($query);
while ($row = $res->fetch()) {
yield new User($row['picture_id']);
}
}
foreach (getUsers() as $user) {
var_Dump($user);
}
Output:
object(User)[5]
public 'picture' => string '2' (length=1)
object(User)[6]
public 'picture' => string '9' (length=1)
object(User)[5]
public 'picture' => string '6' (length=1)
object(User)[6]
public 'picture' => string '1' (length=1)

You can create a custom lazy-loading collection that implements the Iterator interface. Here's an example:
class LazyCollection implements Iterator
{
private $stmt;
private $callback;
public function __construct(PDOStatement $stmt, Closure $callback)
{
$this->stmt = $stmt;
$this->callback = $callback;
}
public function current()
{
$callback = $this->callback; // required because PHP is silly
return $callback($this->stmt->fetch());
}
/* rest of interface implementation */
}
And you would use it like this:
$stmt = $this->pdo->query($sql, PDO::FETCH_ASSOC)
$result = new LazyCollection($stmt, function($row) {
return new User($row['name'], $row['phone'], $row['address']);
});
foreach($result as $user)
{
// $user is an instance of User
}

Probably something like this:
$result = $this->pdo->query($sql, PDO::FETCH_CLASS, 'User');
return $result->fetch();

Related

Object gets stuck in an infinite loop

I am trying to approach just a simple idea of OOP but its been a while since I used something like this.
class UserAPI
{
protected $Email;
protected $APIKey;
public function setEmail($e)
{
$this->Email = $e;
return (new UserAPI)->setEmail($this->Email);
}
public function setKey($k)
{
$k = hash('SHA256',$k);
$this->APIKey = $k;
echo 'Key Wrote';
return (new UserAPI)->setEmail($this->Email)->setKey($this->APIKey);
}
public function getVals(){ echo 'Vals wrote;'; return array('key' => $this->APIKey, 'email' => $this->Email); }
}
print_r((new UserAPI)->setEmail('Example')
->setKey('Password')
->getVals());
As you can probably gather, the (new UserAPI)->setEmail('...') will get stuck in an infinite loop - and so will, eventually, the setKey(); I have been stuck on this for ages and cannot figure out how to return the new Object for continued use.
Any help would be perfect.
Use $this-> inside the class to reference the object itself, and create a new object with new UserAPI().
class UserAPI
{
protected $Email;
protected $APIKey;
public function setEmail($e)
{
$this->Email = $e;
return $this;
}
public function setKey($k)
{
$k = hash('SHA256',$k);
$this->APIKey = $k;
echo 'Key Wrote';
return $this;
}
public function getVals(){
echo 'Vals wrote;';
return array('key' => $this->APIKey, 'email' => $this->Email);
}
}
// this...
$u = new UserAPI(); // create object
$u->setEmail('Example'); // set e-mail
$u->setKey('Password'); // set password
print_r($u->getVals()); // get values
// ...is equivalent to this...
$u = new UserAPI(); // create object
print_r(
$u->setEmail('Example') // set mail
->setKey('Password') // set password
->getVals()); // get values
// ...but only where the class methods return the object
// (ie. not for getValues())
You can propagate the class to another derived call by just returning $this.
But then,

Chaining methods in PHP

$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]);

I can not add array from PDO to constructor PHP

I have probleme when i want add array from PDO to contructor of class news :
class News{
protected $erreurs = array(),
$id,
$auteur,
$titre,
$contenu;
public function __News(array $donnees){
$this->hydrate($donnees);
}
public function hydrate(array $donnees)
{
foreach ($donnees as $key => $value)
{
$method = 'set'.ucfirst($key);
if (method_exists($this, $method))
{
$this->$method($value);
}
}
}
Class Manager with function get news by id :
public function getUnique($id){
$id = (int)$id;
$sql = $this->db->prepare('SELECT id, auteur, titre, contenu FROM news WHERE id = ?');
$sql->bindParam('1', $id);
$sql->execute();
$mang = $sql->fetch(PDO::FETCH_ASSOC);
return new News($mang);
}
And index. php
$db = new PDO('mysql:host=localhost;dbname=new', 'root','');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$manager = new Manager($db);
$uniqueNews = $manager->getUnique(2);
echo 'id : '.$uniqueNews->getAuteur();
But i don't know why the value of auteur do not display . If I'm going about this contructor completely wrong, please point me in the right direction. Thanks.
The constructor of a class should be either __construct() or a method named after the class. I prefer the first option. The second option is for legacy code.
class News{
public function __construct(array $donnees){
// This is where your constructor code goes
}
}
Your constructor should be called __construct()
class News{
protected $erreurs = array(),
$id,
$auteur,
$titre,
$contenu;
public function __construct(array $donnees){
$this->hydrate($donnees);
}
}

save() function Zend Framework

I have found this function in the documentation from Zend, more specific in the Create model and Database Table section ( http://framework.zend.com/manual/1.12/en/learning.quickstart.create-model.html ).
This is in the Application_Model_GuestbookMapper:
public function save(Application_Model_Guestbook $guestbook)
{
$data = array(
'email' => $guestbook->getEmail(),
'comment' => $guestbook->getComment(),
'created' => date('Y-m-d H:i:s'),
);
if (null === ($id = $guestbook->getId())) {
unset($data['id']);
$this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('id = ?' => $id));
}
}
and now i would like to integrate this into my controller, but i have no idea how?
I created an instance of the mapper and tried to pass the info from my decoded json string to it, but I still get errors...:
public function indexAction()
{
$mapper = new Application_Model_GuestbookMapper();
$db = Zend_Db_Table_Abstract::getDefaultAdapter();
$json = file_get_contents('http://data.appsforghent.be/poi/apotheken.json');
$data = Zend_Json::decode($json);
foreach($data['apotheken'] as $row)
{
$mapper->save();
}
}
I know i have to pass the $data to the save() function but I have no idea how... The model won't fit the json-url, I just wanted to show how I retrieve and decode the json.
Can anybody help me?
What you need to pass in to the $mapper->save(); is an instance of Application_Model_Guestbook. So hopefully you have a class Application_Model_Guestbook in which you define the possibility to set a data array as its attributes, for example like this:
class Application_Model_Guestbook {
private $email,$comment,$created;
public function __construct($data) {
$this->email = $data['email'];
// etc add other variables
}
public function getEmail() {
return $this->email;
}
}
Then to call that, use:
foreach($data['apotheken'] as $row)
{
$guestbook = new Application_Model_Guestbook($row);
$mapper->save($guestbook);
}
I have not tested this specifically, but it should give you an idea of how to achieve what you want to do.

Logging Queries with execution time with PDO - autocomplete not working

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);
}
}

Categories