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.
Related
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.).
I have multiple classes set up and they all need to access the database, which they do. The trouble comes when I want to use a function from one class inside another.
class General
{
private $_db = NULL;
private $_db_one;
private $_db_two;
private $offset;
public function __construct ( PDO $db ) {
$this->_db = $db;
$this->_db_one = 'lightsnh_mage1';
$this->_db_two = 'lightsnh_inventory';
$this->offset = 10800;
}
public function getTableNames() {
$sql = 'SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = "BASE TABLE" AND TABLE_SCHEMA="' . $this->_db_two . '"';
$statement = $this->_db->query($sql);
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
This works fine and then my other class connects the same way. As you will see in my "Distributors" class below, I instantiate my "General" class in the constructor. As I am learning while I write, I cant help but feel that there is a more versatile way or efficient way to connect.
class Distributors
{
private $_db = NULL;
private $_db_one;
private $_db_two;
private $_source_tbl;
public $lights;
public function __construct ( PDO $db ) {
$this->_db = $db;
$this->_db_one = 'lightsnh_mage1';
$this->_db_two = 'lightsnh_inventory';
$this->_source_tbl = 'distributors';
// is this the best way to get functions from another class inside of this class? I have 10 classes I will need to repeat this for.
$this->lights = new General($db);
}
public function getInventorySources() {
// calling function from General class inside my distributor class
$tables = $this->lights->getTableNames();
// using result of General function inside of a function from Distributors class
$sql = 'SELECT * FROM `' . $tables . '` WHERE `exclude` = 0';
$statement = $this->_db->query($sql);
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
Singleton is just another form of global state, which is bad. You should always avoid it.
From your code example,
public function __construct ( PDO $db ) {
$this->_db = $db;
$this->_db_one = 'lightsnh_mage1';
$this->_db_two = 'lightsnh_inventory';
$this->_source_tbl = 'distributors';
// is this the best way to get functions from another class inside of this class? I have 10 classes I will need to repeat this for.
$this->lights = new General($db);
}
When you do instantiate this way $this->lights = new General($db); you take General class from global scope. So that, mocking and unit-testing is almost impossible.
Instead you should, inject an instance of General just like as you do for PDO.
Like this:
public function __construct (PDO $db, General $general)
{
$this->_db = $db;
$this->_db_one = 'lightsnh_mage1';
$this->_db_two = 'lightsnh_inventory';
$this->_source_tbl = 'distributors';
// is this the best way to get functions from another class inside of this class? I have 10 classes I will need to repeat this for.
$this->lights = $general;
}
And you would use it this way:
$pdo = new PDO(...);
$pdo->setAttribute(...);
$general = new General($pdo);
$distributors = new Distributors($pdo, $general);
is this the best way to get functions from another class inside of
this class? I have 10 classes I will need to repeat this for.
Yes, you should repeat that, not instantiation, but dependency injection. This makes your code more maintainable and does not introduce global state.
Apart from that, your General class seems obvious violation of the Single-Responsibility Principle.
You should use singleton for getting the DB in your classes,
or use some ORM.
about mysql class with singleton:
Establishing database connection in php using singleton class
I don't know what problems you run into but i think the function getTableNames whil return an object or an array so the result in $tables is not a string do a var_dump($tables); to see what is in $tables
Try to google your way out from there.
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.
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));
Currently I have a class called user that I want to create with different variables, but I think I'm doing it wrong.
Currently I have a class "Unit" with these two functions
public function __construct($table, $id) {
require_once('database.php');
require_once('app.php');
require_once("postmark.php");
$this->table = $table;
$this->valid = true;
if(!$id) {
$this->valid = false;
}
$this->populate($id);
}
public function populate($id) {
$db = new DB();
$q = $db->where('id', $id)->get($this->table);
$resp = $q->fetchAll();
foreach ($resp as $row) {
foreach ($row as $key=>$value) {
if(!is_int($key))
$this->$key = html_entity_decode($value, ENT_QUOTES);
if(is_null($value)) {
$this->$key = null;
}
}
}
if(count($resp) <= 0) $this->valid = false;
$verdict = !$db->error;
$db = null;
unset($db);
return $verdict;
}
And then my "User" class extends it like so
public function __construct($id, $hash = null, $verify = null, $api = null) {
if($api)
$value = $this->apiToId($api);
else if($verify)
$value = $this->verifyToId($verify);
else if($hash)
$value = $this->hashToId($hash);
else
$value = $id;
parent::__construct("users", $value);
}
But I can't help but think this is poor in design. A few things I have seen in the past are the use of ampersands, possibly making it so I could do
$user = new User()->fromId($id);
Or
$user = new User()->withHash($hash);
Instead of passing it a long list of null params. That or I could improve the way inheritance works. While I like to think I know what I'm doing with PHP, I'd really like some help looking in the right direction. PHP's docs are so cumbersome, that I never no where to look, but always find cool useful tools. I'm wondering how I can improve this for more flexibility and structure.
Move includes to the very top of your php file. Anything that needs to be conditionally included is probably poorly designed.
Your unit class should be declared as abstract. This prevents anyone from instantiating a unit. You can only declare subclasses of it.
Any functions relating to your class should be declared as methods. Thus, the example given in an answer now-removed is a terrible choice. The function alloc really should be a static function defined in User. Code snippet at bottom.
Your init functions should be declared as static and return a new instance of the class. Defining an instance of the class to re-instantiate the class is just a bad idea.
Your database connection should use a Singleton pattern. Look it up if you need to.
Post your full code and comment on this answer if you'd like some help implementing all of this.
$user = User::initWithHash($hash);
//your create method:
/**
* Creates and returns a new instance of the class. Useful
* #return an instance of User.
*/
public static function create() {
return new User();
}