Is there any way, to pass results of PDO as parameters of the constructor?
Let's say, I have the following class:
class Test
{
private $value1;
private $value2;
function __construct($val1, $val2)
{
$this->value1 = $val1; $this->value2 = $val2;
}
}
Then, via PDO driver I select some data from DB, let's say:
SELECT price, quantity FROM stock
$results = $query->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Test");
Right now, PDO passess these values directly to the class fields, and bypassing the constructor.
Maybe I am missing something, but I want to pass results from the query to the constructor.
Constructor cannot be query-dependent, I want to be able to instantiate this class even without using PDO.
The only way I figured out was using FETCH_FUNC constant and providing a function to create the object through the constructor.
function rowMapper( $price, $quantity)
{
return new Test( $price, $quantity);
}
$results = $query->fetchAll( PDO::FETCH_FUNC, "rowMapper");
Now your objects will only be created using your constructor, instead of having PDO injecting values in private data and breaking the encapsulation.
Maybe I am missing something, but I want to pass results from the query to the constructor.
Results from the query can be assigned to your object properties directly with PDO::FETCH_CLASS
we also need a constructor able to overwrite values from PDO if needed, so remove PDO::FETCH_PROPS_LATE from the fetch.
function __construct($val1=null, $val2=null) {
// now the constructor is called after the fetch
// if you pass values to it, they will be assigned
if ($val1!==null) $this->price = $val1;
if ($val2!==null) $this->quantity = $val2;
}
we have two ways of instantiating the object
PDO
$stmt = $db->prepare('SELECT price, quantity FROM stock');
$stmt->setFetchMode(PDO::FETCH_CLASS,'Test');
$result = $stmt->fetch();
new
$result = new Test(25,100);
if you want to work with the values which come from the database, you can do so in the constructor, still leaving FETCH_PROPS_LATE out.
function __construct($val1=null, $val2=null) {
if ($val1!==null) $this->price = $val1;
if ($val2!==null) $this->quantity = $val2;
$this->formattedPrice = number_format($this->price,'2');
}
I ran into this situation while building a PDO Repository for my entities. I don't use this approach because I don't like having to match the parameters one to one. This was the closest I could get to mimicking a constructor, by using a static factory pattern.
class User
{
private $id;
public $name;
public $isMinor;
// make sure parameters are in the same order as the mysql result
public static function buildFromPdo($id, $age, $name)
{
$user = new self;
$user->id = $id;
$user->name = $name;
$user->isMinor = $age < 18;
return $user;
}
public function getId()
{
return $this->id;
}
}
$stmt = $db->prepare('SELECT id, age, name FROM users');
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_FUNC, "User::buildFromPdo");
"$result" will be an array of User objects.
[I edited this answer as my previous answer is not accurate anymore.]
FETCH_CLASS does fetch into private properties. Please refer to this answer.
In your case you could pass them like this:
$results = $query->fetchAll(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, "Test", array('val1', 'val1'));
Read about the 3rd parameter for fetchAll() here.
Use PdoObject::fetchAll(\PDO::FETCH_CLASS), this will return what exactly you are wanting.
Nevertheless, if you need something specific to pass follow the below code snippet
$sql = "SELECT * FROM ".$table. " WHERE id=$id";
$sth = $dbAdapter->prepare($sql);
$sth->execute();
//inject anything you want
$result = $sth->fetchAll(\PDO::FETCH_CLASS, $tableClass, array($pass1, $pass2, $dbAdapter));
Related
So I've used PDO::FETCH_CLASS to make a new object, filling the properties with values from the columns of the same name in the database along the lines of:
$db = Connection::get();
$sql = $db->prepare("SELECT * FROM table WHERE id = :id");
$sql->bindParam(":id", "1");
$sql->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'table');
$sql->execute();
$newobj = $sql->fetch();
Is there an opposite for inserting an object's properties into their corresponding columns in the table, to save typing a whole load of bindParam() and a long SQL query?
Many thanks
this was a fun one to do
One caveat is this will take all the public properties in the class and try to use them as part of the query. You could filter them more if you really need to.
We are going to be using what is known as reflection.
This is sort of a double edged sword.
On the one hand we can put this in a base class and extend it with all your objects and it would work just fine for them. If at a later date you add a field to the table, same deal you don't have to change anything, it just works.
On the other hand it takes a small amount of time to process this instead of say having it written as a string. Some of this could be addressed by caching the queries inside protected static properties and by caching the properties themselves ( but not the values )
I will start off by making an Abstract Class. This is a very flexible approach to this problem and it lends it self to being reused - Note: I tested only the Insert method so forgive me if there are any errors in the other parts.
abstract class BaseObject
{
public $id;
protected static $_DB;
public function getDB(\PDO $db ){
if( !self::$_DB ){
//#todo: connect to Database
}
return self::$_DB;
}
public function getId(){ return $this->id; }
public function setId( $id ){ $this->id = $id }
//returns the records id
public function save(){
//if there is no ID then we know it didn't come from the DB
if( $this->id )
return $this->update();
else
return $this->insert();
}
// query format = 'INSERT INTO table (id, ... )VALUES(:id, ... )'
public function insert(){
$this->validate();
$db = $this->getDB(); //localize
$R = new \ReflectionObject( $this );
$props = (array)$R->getProperties()[0];
$names = array_keys( $props );
$sql = 'INSERT INTO '.$this->getTable().' ('.implode(',', $names).' )VALUES( :'.implode(', :', $names).' )';
$params = array_combine(
array_map(function($item){
return ':'.$item;
}, $names),
$props
);
$stmt = $db ->prepare( $sql );
$stmt->execute( $params );
$this->id = $db->lastInsertId(); //don't forget to update the id
return $this->id;
}
// query format = 'UPDATE table SET prop=:prop, ... WHERE id=:id'
public function Update(){
$this->validate();
$db = $this->getDB(); //localize
$R = new \ReflectionObject( $this );
$props = (array)$R->getProperties()[0];
$names = array_keys( $props );
$sql = 'UPATE '.$this->getTable().' SET ';
$set = [];
$params = [];
foreach( $props as $name=>$value ){
$params[':'.$name] = $value;
if( $name == 'id' ) continue;
$set[] = "$name = :$name";
}
$sql .= implode(', ', $set).' WHERE id=:id'
$stmt = $db->prepare( $sql );
$stmt->execute( $params );
return $this->_id;
}
abstract public function getTable();
abstract public function vallidate();
}
Then in your concrete classes you just need to implement the abstract methods, and add the other properties specific to them
class Dude extends BaseObject
{
public $name;
public function getName(){ return $this->name ; }
public function setName( $name ){ $this->name = $name }
public function getTable(){
return 'dudes';
}
public function validate(){
if( empty( $this->name ) ) throw new \Exception( "Name cannot be empty" );
//...etc.
}
}
One thing I happened to think of that you should be aware of, When loading a class -via- the PDO results, the class's constructor is not called. It's been a while sense I loaded a class this way and there may be a way to force it to call the constructor, or I may be just recalling incorrectly. But it's something worth mentioning.
The reason I mention that is that the abstract class needs a PDO instance, so I went and added the getDB() method. This is just a brief example on how you can cache the DB connection for all the classes, ( I was to lazy to do the actual connection part, sorry )
Personally I use what is called a Singleton for my DB needs so I would just call something like this self::$_DB = DB::getInstance();there but that's a story for another day.
I would also suggest adding a delete method, so you get that whole CRUD experience all your programmer fiends keep talking about.
One other major improvement I can think of is you could store some of this stuff in static properties, basically cache it after the first time its ran. That would save some on re-processing ( introspection ) of the class multiple times.
Its a bit tricky though and you would want to further break things down. One tip I can give you on that is make sure to use static::$Var in the base class and not self::$Var so you use whats called Late Static Binding. That's basically the tricky part, because you could have descendant classes with totally different stuff. I'm not sure if this is the correct term but it's kind of a scope resolution problem.
But I will leave these last things up to you. This example should point you down the right path ( or at least a path I know works ), and give you some ideas of what is possible.
One last thing is it's perfectly fine to access properties or call methods using strings like this ( assuming they exist of course )
foreach( $props as $name=>$value ){
//for this we will say $name = 'id'
$method = "get".ucFirst( $name ); // 'getId'
$a = $this->$method(); // calls $this->getId()
$a = $this->$name; //access property $this->id;
}
I just thought I would put that out there to give you another way to access the data that you might find useful.
I think you want to do something like this. refer (The only proper) PDO tutorial
$arr = [1,2,3];
$in = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
$stm = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stm->execute($params);
$data = $stm->fetchAll();
this may give you better idea on this, if I have not understood your question properly please let me know.
I´m wondering how to work with database in classes. For example how to write queries for easy future edit and by OOP rules.
First example:
class User {
public function select($query) {
//work with data
return $data;
}
public function insert($query) {
//work with data
}
}
In this example user (me) add queries for each method. For me, big disadvantage is that I must remember correct form of my query. This example is easy to understand, but in case I will work with more complex queries, it would hard to write correct form of query.
Next example:
class User {
const select = "SELECT * FROM user WHERE ID = ?";
const insert = "INSERT INTO user VALUES (?)";
public function select($id) {
//work with data
return $data;
}
public function insert($id) {
//work with data
}
}
I defined each query as contstant. This form of class is very handy but on the other hand if I want to get ID of user I can't, because I would know name of ID field in the database. This problem I solve in third example
Third example:
class User {
private $table, $idField;
public function setDatabaseInformation($table, $idField) {
$this->table = $table;
$this->idField = $idField;
}
public function select($id) {
//work with data
//query as SELECT * FROM $this->table WHERE $this->idField = ?
return $data;
}
public function insert($id) {
//query as INSERT INTO $this->table VALUES (?)";
//work with data
}
}
Personaly I think, this example is the best, because there I have everything what I want and I can't rememeber forms of queries. On the other hand, this example is a bit longer, very universal and I don't know it is correct by OOP rules.
My questions are: Is there any other options how to write queries in database? Is the last example correct by OOP rules? Which way do you use?
Thanks for suggestions.
Abstract the whole thing inside the class! You want to write code which looks like this in the end:
$database = new PDO(...); // or whatever
$userStorage = new UserStorage($database);
$user = $userStorage->getById(42);
$users = $userStorage->getByName('Joe');
class UserStorage {
protected $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function getById($id) {
$stmt = $this->db->prepare('SELECT ... WHERE id = ?');
$stmt->execute([$id]);
...
return $user;
}
...
}
You only want to write each logical query once in one specific, authoritative place. For each possible logical "action", create a method (one method to get users by id, one to get them by name, etc.). Each method can be as complex or simple as necessary internally, but it is the one place where you write the specific query to get the specific data. Lastly: dependency inject your database connection.
I'm trying to learn OOPHP and I think I'm getting there. I just don't know what the best way of outputting multiple database rows is.
The first method I've tried is doing the outputting normally within the function, but I think this kind of defeats the purpose of OOP.
I've also tried setting a public $list within the class that the database query pushes all the data to and then looping through the data outside the code, but ideally I'd like to create multiple instances of the object from the query and use their public variables (id, name, and eyecolour in my test case) to get the data so that the data always comes from the same place. But how do I create multiple objects from a query within the class and then how do I loop through them to display them, and how do I differentiate between them or just target a specific object with a specific value?
I have trawled through multiple topics but they all seem to focus on the problem in a specific situation. I'd like to learn it in this simple sense so I can then apply it to bigger projects.
Here is the code that I have been playing around with:
class Ignition {
public function dataConnection() {
return new PDO('mysql:host=localhost;port=8889;dbname=oop_testing','root','root',array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
}
}
class People {
private $db;
// Each person has a
public $id;
public $name;
public $eyecolour;
public $list = array();
public function __construct() {
$this->db = new Ignition();
$this->db = $this->db->dataConnection();
}
public function getList() {
echo "People:<br /><br />";
$sql = $this->db->prepare("SELECT * FROM people");
$sql->execute();
//$this->list = $sql->fetch(PDO::FETCH_ASSOC);
while($person = $sql->fetch(PDO::FETCH_ASSOC)) {
array_push($this->list, $person);
}
/*while($person = $sql->fetch(PDO::FETCH_ASSOC)) {
echo $person['name'] . "<br />";
}*/
}
public function getIndividual($search, $by = "id") {
$sql = $this->db->prepare("SELECT * FROM people WHERE $by = :search");
//$sql->bindParam(":by", $by);
$sql->bindParam(":search", $search);
$sql->execute();
$individual = $sql->fetch(PDO::FETCH_ASSOC);
$this->id = $individual['id'];
$this->name = $individual['name'];
$this->eyecolour = $individual['eyecolour'];
}
}
$people = new People();
$people->getList();
/*echo "<pre>";
var_dump($people->list);
echo "</pre>";*/
foreach ($people->list as $person) {
echo $person['name'];
}
$people->getIndividual(1);
echo "<br />Name: " . $people->name;
echo "<br />Eye Colour: " . $people->eyecolour;
The best option would actually to get a list of People objects how you do that is up to you. I would utilize PDO::FETCH_CLASS and use static functions something like this:
class People {
public $id;
public $name;
public $eyecolour;
public function display() {
echo "Id: {$this->id} name: {$this->name} eyecolour: {$this->eyecolour} <br>";
}
public static function get() {
$result = self::db()->prepare("select * from people limit 1");
$result->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,'People');
$result->execute();
return $result->fetch();
}
public static function getall() {
$result = self::db()->prepare("select * from people");
$result->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,'People');
$result->execute();
return $result->fetchAll();
}
public static function db() {
return new PDO("mysql:host=127.0.0.1;dbname=mydb", 'root', '', array(PDO::ATTR_PERSISTENT => true));
}
}
$person = People::get();
$person->display();
$all_people = People::getall();
foreach($all_people as $single_person) {
//$single_person is an instance of People
$single_person->display();
}
Obviously you can add search criteria to the get() and getall() methods like you have in your functions. I was just doing this as an example.
To practice OO, you may begin with setting all variables in the class to private (even when tempted to use public) and only use accessor functions. With this rule, because we are lazy, it is more likely that we put code in the correct place (inside the class).
Also, a good rule is that one object in real life should be one class.
In this specific case, I would create a class named Person with the different properties of a person (eyecolor, height etc.). The People class can create Person objects from what it retrieves from the database and store them in an array (maybe indexed by ID or name).
The Person class can take the whole row from the database as argument to it's constructor, and it then has to do the dirty work of setting/checking all variables itself. This is good for laziness and OO (no setter functions).
You may have an accessor function in People which returns a Person (searches for name, ID, etc.).
This question is very similar to the one already asked here: PDO using PDO::FETCH_PROPS_LATE and __construct() call?
However, the accepted answer in that question doesn't really answer how to pass fetched values into the constructor of the class, if that's even possible. Here's some reduced code:
<?php
class Course {
private $name;
private $code;
private $prerequisites;
public function __construct($name, $code) {
if(!is_string($code) || mb_strlen($code) !== 7) {
throw new \InvalidArgumentException('Invalid code.');
}
$this->name = $name;
$this->code = $code;
$this->prerequisites = array();
}
public static function getAllCourses() {
$sql = 'SELECT `name`, `code` FROM `courses` ORDER BY `code`;';
$db = new \PDO(' ... ', DB_USER, DB_PASS);
$stmt = $db->query($sql);
$stmt->setFetchMode(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, 'Course', array('name', 'code'));
return $stmt->fetchAll();
}
}
?>
The problem with this code is that the literal strings "name" and "code" get passed to the constructor. However I want to pass the actual values of the name and code to the constructor (obviously).
Can this be done? If yes, how? Do I use bindColumn()?
basically, any PDO::fetch_something() method WILL ASSIGN THE PROPERTIES.
so you just ned properties, that have the same name as the columns, as you have it correctly.
you don't even need getters or setters or a constructor!
you can pass values to the constructor of the class like this:
$stmt->setFetchMode(\PDO::FETCH_CLASS,
'Course',
array(<value of col1>, <value of col2>));
note, that you do not pass the name of the columns, but the values, in the array!
but that is tricky! be sure to have set the fetchMode properly!
if \PDO::FETCH_PROPS_LATE is set it will FIRSTLY call the __construct and SECONDLY set the properties. so the values you have passed to the __construct via the array will be overwritten.
if you haven't set \PDO::FETCH_PROPS_LATE it will do it reverse.
just try it.
I am wondering whether or not it is possible to elegantly map the results of a PDO query to an array member in a class rather than have them floating about as public properties of that object.
Say I have the (condensed) following:
class DBObject {
protected
$record = array();
function __construct(array $record) {
if(!empty($record)) {
$this->loadRecord($record);
}
}
}
Ideally, I want to call the constructor with an array of values passed from the database, rather than use __set or any other weird methods. So using PDO's existing API would be great.
My rough get_all function at the moment has got this far:
static function get_all() {
$class = get_called_class();
$results = DB::factory()->query('SELECT * FROM ' . $class . ' ORDER BY ID');
$results->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, $class);
return $results;
}
NB: I'm running PHP 5.3 and MySQL through PDO, and already know this problem is solveable using __set, but I explicitly want to avoid using it in favour of something more performant.
You don't need to pass arguments to a constructor to make a class with private members using PDO::FETCH_CLASS. You can do something like this:
<?php
class Songs
{
private $artist;
private $title;
public function __construct()
{
}
public function get_artist()
{
return $this->artist;
}
public function get_title()
{
return $this->title;
}
private function set_artist($artist)
{
$this->artist = $artist;
}
private function set_title($title)
{
$this->title = $title;
}
}
I'm actually doing that on a demo site that I built. It works just fine with PDO::FETCH_CLASS. By default, FETCH_CLASS creates objects by populating the fields BEFORE the constructor. Think of it as bypassing the constructor. And it will do this with private members.
If you'd rather pass arguments to the constructor you can do your query like this:
$obj = $statement->fetchALL(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'Songs', $params);
In that case your constructor would look like this:
public function __construct($params)
{
$this->artist = $params[0]['artist'];
$this->title= $params[0]['title'];
}
Removed previous code
Right, can't you do something like this:
class DBObject {
protected $record = array();
function __construct($record = null) {
if(null === $record){
$obj_vars = get_object_vars($this);
$cls_vars = get_class_vars(get_class($this));
$this->$record = array_diff_key($obj_vars, $cls_vars);
}else{
$this->record = $record;
}
}
}
The problem with this however is that the values are still available as public members.
But what it will do is compare 'pre-defined' (class) members to the actual (object) members.
Since PDO will create new members in the object you can use array_diff_key to get the 'new' members.
Yes, this will still not pass them through your constructor.
How about using magic __set() method:
<?php
class MyClass
{
protected $record = array();
function __set($name, $value) {
$this->record[$name] = $value;
}
}
$pdo = new PDO("mysql:host=localhost;dbname=db", 'user', 'password');
$results = $pdo->query('SELECT * FROM table');
$results->setFetchMode(PDO::FETCH_CLASS, 'MyClass');
PHP will call this magic method for every non-existent property passing in its name and value.