PDO: Fetch class option to send fields to constructor as array - php

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.

Related

PHP equivalent to C++ Templates

In C++, I can create template classes like this:
<template class l> class foo {
private:
l myVar;
foo::foo(l var) {
myVar = var
}
l getVar() {
return myVar
}
// etc...
}
Can I do something similar in PHP?
Reason I'm asking:
Let's say I have a class in PHP that conceptualizes a MySQL table:
class Table {
private $conn;
private $name;
function __construct($connection, $tablename) {
$this->conn = $connection;
$this->name = $tablename;
}
// etc..
}
I can make class functions that handle certain types of queries:
function GetAll() {
$sql = "SELECT * FROM " . $this->name;
$data = $this->conn->query($sql);
return $data;
}
Of course, this is just an example, but hopefully you get the picture.
What I'm looking to do is let's say I have a class called "Users". Before I return $data, I would like to put it in a User object. Of course, to be generic, I'd like to be able to put it into ANY class I like. Just like a C++ template.
Note: A PHP interface will not work for this because I'd need to instantiate the class, not just use a given set of functions.
Edit:
So, I found a workaround for MY problem.
However, I'd still like an answer to whether there is a PHP equivalent.
So, I figured out a work around, but I would still appreciate other answers. I would still like to know if there is a PHP equivalent. However, this is what I've got:
class Table {
private $conn;
private $name;
private filter; // a callback
function __construct($connection, $tablename, $callback) {
$this->conn = $connection;
$this->name = $tablename;
$this->filter = $callback;
}
// etc..
}
And then in GetAll:
function GetAll() {
$sql = "SELECT * FROM " . $this->name;
$data = $this->conn->query($sql);
$data = $this->filter($data);
return $data;
}
Then, I could pass a callback that filters the data how I want it.
I think that will work.

Is there a way to set only defined properties when using fetch_object on a mysqli_result?

If I have a class:
class ExampleClass {
private $thing1;
private $thing2;
}
I can use fetch_object('ExampleClass') on a mysqli_result object to create an instance of ExampleClass. Using this method, the private properties of ExampleClass will be set, provided the query has columns with the same names.
In addition to those properties, other public properties will be added for any other columns in the query. Is there any way to avoid this? I couldn't find anything about it in the php documentation for fetch_object.
If I set up ExampleClass with a constructor like this
function __construct($properties = []) {
foreach ($properties as $key => $value)
if (property_exists($this, $key))
$this->$key = $value;
}
I can get the row from the result set using fetch_assoc instead of fetch_object, and then create a new ExampleClass with the resulting array as an argument. This achieves what I am going for, but I was hoping for something more direct.
For one of my projects I have built a system just like that.
All classes are derived of the basic abstract Object class which, among others, offers a cloneInstance() method. Then, in the concrete implementing class, I'm simply going to use it ('I'm assuming that $pdo is somehow accessible here, for brevity):
Note that cloneInstance() uses reflection to check if the target instance actually has a euqlly named property ($drfl->hasProperty()).
abstract class Object {
protected function cloneInstance($obj) {
if (is_object($obj)) {
$srfl = new ReflectionObject($obj);
$drfl = new ReflectionObject($this);
$sprops = $srfl->getProperties();
foreach ($sprops as $sprop) {
$sprop->setAccessible(true);
$name = $sprop->getName();
if ($drfl->hasProperty($name)) {
$value = $sprop->getValue($obj);
$propDest = $drfl->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($this,$value);
}
}
}
return $this;
}
class MyAutomaticClass extends Object {
// static loader
public static function load($id) {
$result = null;
$sql = 'SELECT * FROM mytable WHERE id=:id';
$stmt = $pdo->prepare($sql, array(':id' => $id));
$list = $stmt->fetchAll(PDO::FETCH_OBJ);
if (count($list)) {
$result = new MyAutomaticClass($list[0]);
}
return $result;
}
// constructor makes use of base Objects cloning feature
public function __construct($obj=null) {
if (is_object($obj)) {
$this->cloneInstance($obj);
}
}
}

php oop constructor

OK. here is what I'm trying to do:
class Image{
public $_image;
public $_extension;
public $_mime;
public $_size;
public $_location;
public $_description;
public function __construct($image, $location){
$this->_image = $image;
$this->_location = $location;
$this->_extension = getExtension();
$this->_mime = getMime();
$this->_size = getSize();
}
private functions fallow.....
}
But I keep getting an internal server error when I try to run it. When I comment out the method calls it works. So the question is can I call methods from inside the constructor or am I doing something wrong with the methods.
Do your functions getExtension, getMime and getSize exist? Are they methods on this class? If they are methods, they need to be called with $this->... as in
$this->_extension = $this->getExtension();
If they are not methods, and are functions, you need to make sure the files that contain/define them are loaded before you run the constructor.
Well ..this fragment of code will work as expected:
class Foo
{
protected $secret = null;
public function __construct( $data )
{
$this->secret = $this->makeSecret($data);
}
public function makeSecret( $data )
{
return md5( $data );
}
}
$bar = new Foo( 'lorem ipsum' );
That is not a problem.
But you should know, that is considered to be a bad practice - to do computation/work in the constructor. It makes that class practically untestable. Instead, if you need to perform some computation before "releasing" the object to the rest of the code, you should use a factory. Something along the lines of :
class ImageFactory
{
public function build($image, $location)
{
$instance = new Image($image, $location);
$instance->prepare();
return $instance;
}
}
The class would need some changes:
class Image
{
protected $_image; // you were leaking abstraction
protected $_extension;
protected $_mime;
protected $_size;
protected $_location;
protected $_description;
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
}
public function prepare()
{
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
private functions fallow.....
}
Now when you need to create new object you do:
$factory = new ImageFactory;
$image = $factory->build( $file, '/uploads/' );
Of course the instance of ImageFactory can be reusable, and if all your images use the same $location, then you would pass that variable to factory at the initialization. And the factory would be able to "remember it" and pass to all the images it creates:
$factory = new ImageFactory('/uploads/');
$img1 = $factory->build( $file );
$img2 = $factory->build( $something_else );
This is actually how one should deal with creating multiple objects, which all need access to same DB connection instance.
Yes, you can call methods from within the constructor. Remember that the __construct() magic method was implemented in PHP 5. Prior to that, you created a function named the same as your class which acted as your constructor so depending on your PHP version, that could be a problem.
Additionally, the function calls you are making, are they in the class or external? If they are inside the class you need to call them this way:
$this->_extension = $this->getExtension();
You didnt specified what error you are expiriencing clearly. But try calling you class methods even inside the class using this keyword, otherwise it would not work:
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
Would be a better idea to post your code for the methods you wrote. There could be something wrong within them as well. Possibly forgetting to return a result or something...

PHP PDO: Do the fetch styles FETCH_CLASS and FETCH_INTO fetch into private object properties?

Pretty short question, here is an example:
$prepared = $this->pdo->prepare("SELECT * FROM Users WHERE ID = :ID");
$statement = $prepared->execute(array(":ID" => $User_ID))
$result = $statement->fetchAll(PDO::FETCH_CLASS, "User");
//OR
$User = new User();
$result = $statement->fetch(PDO::FETCH_INTO, $User);
(written from top of the head, could contain syntax errors)
Do those two directly fetch into the private properties of said objects?
I read it also circumvents the __construct function, so will it circumvent private status too?
Very short answer: Yes it will.
class Foo
{
private $id;
public function echoID()
{
echo $this->id;
}
}
$result = $statement->fetchAll(PDO::FETCH_CLASS, "Foo");
$result[0]->echoID(); // your ID
Aside:
This will cause syntax errors $statement->fetchAll(PDO::FETCH_INTO, $User);. You can't use FETCH_INTO with the fetchAll method.
But event with PDO::FETCH_CLASS there is a problem for private properties for subclasses.
E.g.
class Animal
{
private $color;
public function getColor()
{
return $this->color;
}
}
class Cat extends Animal
{
}
$statement->setFetchMode(PDO::FETCH_CLASS, "Cat" );
$someCat = $statement->fetch();
echo $someCat->getColor(); //empty
print_r( $someCat );
/*
now have strange output like:
[color:Animal:private] =>
[color] => grey
*/
But if you set the property to protected - it works fine
The reason you can't access private properties on a super class is because those properties are out of scope. Subclasses don't take on the private attributes of their parent classes, including variables and functions.
edit: Thanks for clarifying your question but it does make my answer look a bit ridiculous here. :p
You could try:
class Foo {
private $id;
public function __set($prop, $val) {
$this->$prop = $val;
}
public function __get($prop) {
return $this->$prop;
}
}
$result = $statement->fetchAll(PDO::FETCH_CLASS, "Foo");
$result[0]->id();

PHP - passing variable from one object to another troubles

I've recently started to work with OO PHP. As a training practice I'm trying to write some simple classes. I have trouble passing a variable from one to another class. Is it even possible?
class group
{
public $array = array();
public function person($name,$surname)
{
$this->person = new person($name,$surname);
}
public function __destruct()
{
print_r($this->array);
}
}
class person
{
public function __construct($name,$surname)
{
$this->name = $name;
$this->surname = $surname;
}
}
$A = new group();
$A->person("John","Doe");
What I want to archieve here is to pass person as another member of group (by simply putting it in group array) for further modifications and sorting. Been googling around but found nothing.
Please forgive me if it's a dumb one. ;)
I'm not sure I totally understand but I think you want:
Class group {
public $members=array();
public function person($name,$surname) {
$this->members[]=new person($name,$surname);
//Creates a new person object and adds it to the internal array.
}
/*...*/
}
A better alternative (seperation of intent) would be:
Class group {
public $members=array();
public function addPerson(person $p) {
$this->members[]=$p;
//Avoids this function need to know how to construct a person object
// which means you can change the constructor, or add other properties
// to the person object before passing it to this group.
}
/*...*/
}
The fix is changing
public function person($name,$surname)
{
$this->person = new person($name,$surname);
}
to
public function person($name,$surname)
{
$this->array[] = new person($name,$surname);
}
$this->person is not being stored in the array otherwise, and is overwritten with each call.
Your group class could improve it's OO by:
changing $array to be more descriptively named
changing the function name person to something more meaningful, like add_person
You should define your properties ('name', 'surname') and give them a suitability visibility
class group
{
public $array = array();
public name;
public surname;
...
Reference: http://php.net/manual/en/language.oop5.visibility.php

Categories