PDO has a predefined fetch modes that can be passed into the fetch
and fetchAll
methods. I am mainly concentrating on FETCH_CLASS
in my issue.
In order to load the properties and values into my class I created a trait to allow for magic methods __set
.
trait Entity {
public function __construct() {
$this->_attributes = func_get_args();
}
public function __set($prop, $value) {
if (in_array($prop, $this->_attributes))
$this->$prop = $value;
}
}
This then can be easily used across the application by using the trait in a class.
class UserModel {
use Entity;
public function save() {
}
}
The issue I am having now is implementing the magic method __get
to now be able to save the data based off the class properties and values. In order to load data into the UserModel
, I can just create a PDO instance and query like so:
$pdo = new PDO();
$stmt = $pdo->prepare('SELECT user_id, email, username, hash FROM app_users WHERE user_id = 1');
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_CLASS, 'UserModel');
I tried to generate the additional __get
method in my trait like so:
public function __get($prop) {
if(isset($this->$prop)) return $this->$prop;
}
But I am now stuck with how to use the instance to update the row when the PDO instance is outside of its scope. I tried to implement a singleton trait for the PDO like so:
trait Singleton
{
private static $instance;
public static function getInstance()
{
if(self::$instance) return self::$instance;
self::$instance = new self();
return self::$instance;
}
protected function __construct() {}
private function __clone() {}
}
And a really quick extension of the PDO for basic implementation for now.
class PDOWrapper extends PDO
{
use Singleton;
protected function __construct()
{
parent::_construct('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
));
}
public function query($sql)
{
return parent::prepare($sql);
}
}
Which then changes my whole approach to fetching the row into the class to look something like this:
$stmt = PDOWrapper::getInstance()->query('SELECT user_id, email, username, hash FROM app_users WHERE user_id = 1');
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_CLASS, 'UserModel');
Now inside my save()
method, I have access to the same PDO instance using PDOWrapper::getInstance()
. The issue I have is that you need to know what properties where loaded from the SQL query to get the necessary values.
For example, prior, I loaded the email
, user_id
and username
columns into the class. I want to update the password for that user. This property does not exist in the first fetch. How can I check what properties exist in the class?
// Won't work
public function save() {
$stmt = PDOWrapper::getInstance()->query('UPDATE app_users SET hash = ? WHERE user_id = ?');
$stmt->execute(array($this->__get('hash'), $this->__get('user_id')));
}
I need a solution that is dynamic to the properties set, for example which should probably all be a batch update:
// Sudo code
PDOWrapper::getInstance()->beginTransaction(); // Will need to be added
foreach($this->props as $prop)
PDOWrapper::getInstance()->query('UPDATE table SET prop = value where user_id = value')->execute(array($this->$prop, $this->user_id));