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.
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 have an object which pulls back details about the site. Basically the name address etc. One of the fields it pulls back is IDCounty. see below. I then want to feed this into a new instance of another object and it automatically give me the county name. Here is what i have
so my system object
class System{
private $db;
public $Ad1;
public $Ad2;
public $County;
public $IDSystem;
//connect to database
public function __construct($IDSystem ='1') {
$this->db = new Database();
$this->IDSystem = $IDSystem;
}
//get address
public function ContactInfo() {
$Query = sprintf("SELECT BSAd1, BSAd2, IDCounty FROM BusinessSettings WHERE IDBusinessSettings = %s",
GetSQLValueString($this->IDSystem, "int"));
$Query = $this->db->query($Query);
$Result = $this->db->fetch_assoc($Query);
$this->Ad1 = $Result['BSAd1'];
$this->Ad2 = $Result['BSAd2'];
$County = new County($Result['IDCounty']);
$this->County = $County;
}
//end address
}
As you can see this object is calling another County and setting the $County to $this->County. My details for that are below
class County {
public $IDCounty;
private $db;
public function __construct($IDCounty) {
$this->db = new Database();
$this->IDCounty = $IDCounty;
$Query = sprintf("SELECT CountyName FROM County WHERE IDCounty = %s", GetSQLValueString($this->IDCounty, "int"));
$Query = $this->db->query($Query);
$County = $this->db->fetch_assoc($Query);
return $County['CountyName'];
}
}
When I'm calling the object I call it like so
$SiteDetails = new System();
$SiteDetails->ContactInfo();
echo $SiteDetails->Ad1;
echo $SiteDetails->County;
Im getting an error from error reporting on echo $SiteDetails->County; which says
"Catchable fatal error: Object of class County could not be converted to string"
After Googling this error I see the System class is having trouble converting getting the county name from the County class and converting it to $this->County
Unfortunately for me I'm not sure how to fix it though. I thought I could return a value from a function upon instantiation but it seems I'm wrong. Please help. thanks guys.
Whatever return statements a constructor may contain, they will be ignored. A constructor will, by definition, return a new instance of a given class. Read the docs. Though the signature of __construct() shows a return-type of void, you should think of it as void* (a void pointer). That means, it returns a reference to the newly created object. You should replace the return statement with soimething like:
$this->stringValue = $County['CountyName'];
And, because of many other magic-methods, you can implement another one to use a class as a string: the magic __toString method:
public function __toString()
{
return (string) $this->stringValue;//the cast is optional
}
As ever, read through the documentation on some of the other magic-methods. They're quite useful, though I must add, they are/can be slow. you could, equally well implement a getter:
//assuming this is in your constructor:
$this->stringValue = $County['CountyName'];
public function getContyName($default = '')
{
if (!$this->stringValue)
{
return $default;
}
return $this->stringValue;
}
Now, thanks to the $default argument, I can do this:
$county = new County();//not passing any values
echo $county->getCountyName('unknown');//will echo unknown
echo $county->getCountyName();//echoes empty string, because that's the default value of $default...
$county2 = new County('Foobar');
echo $county2->getCountyName('unknown');//echoes Foobar, county is known...
Other than that, your sprintf call isn't making much sense to me. The format used is %s, so I'd expect you pass a string. In fact you're passing GetSQLValueString($this->IDCounty, "int"), which is a the return value of a call to a global function (terrible code smell BTW). Since you're passing 'int' as an argument there, I'd expect the return value to be an int, so why not %d in your sprintf format?
There are many, many other things that need work here (for example: creating a new Database instance without params? really? Why not query using a DB object? why use a global function? Why aren't you using prepared statements? ... Please, read up some more on OOP
Implement the method __toString() in County class.
Example:
public function __toString()
{
return $this->foo;
}
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));
This question already has answers here:
Call to a member function on a non-object [duplicate]
(8 answers)
Closed 9 years ago.
I have this code:
<?php
class guildData {
public $email = NULL;
public $hash_pw = NULL;
public $user_id = NULL;
public $clean_username = NULL;
public $display_username = NULL;
public function selectGuild($g_id)
{
global $db,$db_table_prefix;
$this->g_id = $g_id;
$sql = "SELECT
name
FROM
guild
WHERE
id = '".$g_id."'";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
return ($row['name']);
}
}
?>
<?php echo $guildData->selectGuild(1); ?>
I'm simply getting a 500 error and IDEone gave me this also:
Fatal error: Call to a member function selectGuild() on a non-object in /home/VT00Ds/prog.php on line 32
I can not see the error, can you help me?
You are doing it wrong.
Get rid of the global variables. Instead , if the class needs DB access , then you should inject it in the constructor:
class GuildData
{
// ... snip
protected $connection;
public function __construct( PDO $connection )
{
$this->connection = $connection;
}
// ... snip
}
Your code has potential for SQL injections. Instead of concatenating the query, you should use prepared statements:
$statement = $this->connection->prepare(
'SELECT name FROM guild WHERE id = :id'
);
$statement->bindParam( ':id', $this->g_id, PDO::PARAM_INT );
if ( $statement->execute() )
{
$data = $statement->fetch( PDO::FETCH_ASSOC );
}
You have to instantiate object, before you can use them:
$pdo = new PDO('mysql:host=localhost;dbname=myMagicalDB;charset=UTF-8',
'username', 'password');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$guild = new GuildData( $pdo );
$guild->selectGuild(42);
You might consider separating the part with deals with DB from the domain logic. Essentially letting some other class to handle the fetching and storing the data, while the Guild class manages the logic. You might find this and this answer relevant.
Do not use public variables. You are breaking the encapsulation of the object by directly exposing the internal data. Instead you should define them as protected or private.
You might also take a hard look at what you are actually keeping there. Why does GuildData need $hash_pw or $clean_username ?
You haven't instantiated $guildData. If you want to use this method without instantiating an object, the method should be static.
class guildData {
public static function selectGuild($g_id) { ... }
}
Then you can call it from
echo guildData::selectGuild(1);
Otherwise, you need to instantiate an object
$guildData = new GuildData();
echo $guildData->selectGuild(1);
Also, you should have some sort of __construct() method in there, in order to set up your member variables.
UPDATE I also noticed an error in your selectGuild() method:
$this->g_id = $g_id;
Sets the value of g_id, which is not defined as a member variable in the class. You must declare g_id as a member variable in your class definition:
class guildData {
public $email = NULL;
public $hash_pw = NULL;
public $user_id = NULL;
public $clean_username = NULL;
public $display_username = NULL;
public $g_id = NULL;
.
.
.
}
Finally, sql_query() is not a PHP method that I've ever heard of. Unless you're using a library that defines these methods, I think you mean mysql_query(). If this is the case, you should stop using mysql_* functions. They're being deprecated. Instead use PDO (supported as of PHP 5.1) or mysqli (supported as of PHP 4.1). If you're not sure which one to use, read this SO article.
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.