Is it possible to store an array as an object property in PHP?
I am building an article class that pulls various information about a research article and stores them as a properties in an object. Since the number of authors vary per research article, I would like to store them as an array in an $authors property, rather than store each author as a separate property. In this code sample, I realize this problem results from working with a poorly designed table, but nonetheless, I would like to see how this code could be used to store an array as an object property.
<?php
Class Article {
public $id;
public $authors;
public $article_name;
public $journal;
public $volume_number;
public $issue_number;
public $article_location;
public function __construct($id, array $authors, $article_name, $journal,
$volume_number, $issue_number, $article_location)
{
$this->$id = $id;
$this->$authors = $authors;
$this->$article_name = $article_name;
$this->$journal = $journal;
$this->$volume_number = $volume_number;
$this->$issue_number = $issue_number;
$this->$article_location = $article_location;
}
}
//function to pull Article information from Articles Table
function getArticle($id){
try {
$query = "SELECT * FROM articles WHERE ID = :ID";
$db = Db::getInstance();
$results = $db->prepare($query);
$results->execute([':ID'=>$id]);
$row = $results->fetch();
$authors = array();
if(!empty($row['author'])){
$authors[] = $row['author'];
}
if(!empty($row['author2'])){
$authors[] = $row['author2'];
}
if(!empty($row['author3'])){
$authors[] = $row['author3'];
}
//This repeats for a while.
return new article($row['ID'],
$authorList,
$row['article_name'],
$row['journals'],
$row['volume_number'],
$row['issue_number'],
$row['article_location']);
} catch (PDOException $e) {
return "Unable to pull articles from the Articles table.";
echo $e->getMessage();
}
}
Yes, it is possible to store an array as a property.
The problem is that you use properties wrong.
$this->$authorList
Is wrong, you should use:
$this->authorList
Your code currently creates properties for your class based on the original property's value - if $article_name has the value of 'ABCD', $this->$article_name creates and fills the property 'ABCD' - being the equivalent of $this->ABCD = $article_name;, meaning you won't be able to access the value in the original property. It's the same with $this->$authors = $authors; - if you are passing an array as $authors, your code will try to store it as a string, making the situation even worse. Removing the $ before $authors solves this issue too.
Also, when you use $authorList[], you are pushing values into a local variable, not into the class property. It's not necessarily the wrong way to do it, as long as you copy the local variable's content into the property, but I would strongly suggest not to use variables named after properties. It makes your code harder to maintain, as it can confuse developers.
Related
So far I've used many objects in my applications but often if I had to for example display for example users' profiles on page I simply got 20 users from database as array using some method in my object and assigned it to view.
Now I want to create application more with models that represent real data. So for each user I should probably have User object with properties .
Here I put sample code to get users from database and to display them in PHP:
<?php
$db = new mysqli('localhost', 'root', '', 'mytest');
class User
{
private $id;
private $name;
public function __construct($data)
{
foreach ($data as $k => $v) {
if (property_exists($this, $k)) {
$this->$k = $v;
}
}
}
public function show()
{
return $this->id . ' ' . $this->name ;
}
}
$result = $db->query("SELECT * FROM `user` LIMIT 20");
$users = array();
while ($data = $result->fetch_object()) {
$data->x = 10; // just to test if property isn't created
$users[] = new User($data);
}
// displaying it on page
foreach ($users as $user) {
echo $user->show() . "<br />";
}
Questions:
Is it the way I should use data from database into model? I mean if should I create object for each record even if the only role of this object would be returning some data to view (for example even not modified by any functions as in this example). Of course I know that often data should be prepared to display or made some calculations or additional data should be retrieved from database before those data could be used to display.
Is there any way to assign object properties from database simpler than using constructor with loop?
First of all, i'd move the DB operations in a separate class, not inline with the User class. You could create an abstract Model class, which the User class would extend and add DB logic to it.
You'd have a select() method to query the database, which would return an array of objects of the class that extended the Model class (the User class in this case).
To answer your questions:
I think it's ok. Most ORMs work this way.
An alternative would be to assign the user row data from the DB to a $data attribute in your User class and use the magic methods __get and __set to access them.
What I'm doing
I'm attempting to get an array of Image objects using the following:
(If I run the raw MySQL query, I am returned 3 distinct rows, all with different values, except for the product_id, obviously.)
$query = 'SELECT * FROM `j_images` WHERE product_id = :product_id';
$stmt = $db_conn->prepare($query);
if($stmt)
{
$I = new \jenis\Product\Image();
$stmt->setFetchMode(PDO::FETCH_INTO, $I);
$result = $stmt->execute(array('product_id'=>$product_id));
if($result)
{
$images = $stmt->fetchAll();
var_dump($images);
}
}
What I Get
An array with 3 jenis\Product\Image objects, but the objects (including references) are identical.
What I expect
An array with 3 \jenis\Product\Image objects, each unique.
Is this because it is fetching into the same object (i.e. $I)? If so, is there a way around this?
However Example 4 in the PHP documentation would lead me to believe that this is possible.
If I follow the example directly from the documentation:
$images = $stmt->fetchAll(PDO::FETCH_CLASS, "\jenis\Product\Image");, I get three separate objects, but all properties are NULL.
Additional Information
Here is a stripped down version of my Image Class:
namespace jenis\Product;
use jenis\DB as DB;
use \PDO as PDO;
class Image
{
public $id;
public $product_id;
public $url;
public static function getImagesByProduct($product_id)
{
… code outlined above …
}
}
The code outlined above is executed as a static method (e.g. Image::getImagesByProduct($product_id);
The reason this wasn't working was because I had developed my constructor so that optional parameters could be passed.
For example:
function __construct($name='', $description='')
{
$this->name = $name;
$this->description=$description;
}
Because the constructor is utilized when FETCH_CLASS is called, this was causing my variables to be NULL, as the properties are not passed as parameters. As #Digital Chris noted, I needed PDO::FETCH_PROPS_LATE which allowed the properties to be set after the constructor was called.
Codeigniter can return a database query as generic "Object" like:
$q = $this->db->get("some_table");
$obj = $this->q->row();
$var = $obj->some_property
In my case I want to make a PHP class who's public variables are 1 for 1 with the database columns, along with some public methods. Is there a quick one-shot way to cast or convert the generic "Row" object into my custom class object? I've read posts that hint that it is certainly possible, but most involve a really hacky serialize/deserialize solution. In the past I have just done:
public function __construct($row) {
$this->prop = $row->prop;
$this->id = $row->id;
$this->value = $row->value;
}
And I find this is very tedious and makes ugly code.
See the third section under result():
CodeIgniter User Guide: Generating Query Results
You can also pass a string to result() which represents a class to instantiate for each result object (note: this class must be loaded)
$query = $this->db->query("SELECT * FROM users;");
foreach ($query->result('User') as $row)
{
echo $row->name; // call attributes
echo $row->reverse_name(); // or methods defined on the 'User' class
}
I have a custom class object in PHP named product:
final class product
{
public $id;
public $Name;
public $ProductType;
public $Category;
public $Description;
public $ProductCode;
}
When passing an object of this class to my Data Access Layer I need to cast the object passed into a type of the product class so I can speak to the properties within that function. Since type casting in PHP works only with basic types what is the best solution to cast that passed object?
final class productDAL
{
public function GetItem($id)
{
$mySqlConnection = mysql_connect('localhost', 'username', 'password');
if (!$mySqlConnection) { trigger_error('Cannot connect to MySql Server!'); return; }
mysql_select_db('databaseName');
$rs = mysql_query("SELECT * FROM tblproduct WHERE ID='$id';");
$returnObject = mysql_fetch_object($rs, 'product');
return $returnObject;
}
public function SaveItem($objectToSave, $newProduct = false)
{
$productObject = new product();
$productObject = $objectToSave;
echo($objectToSave->Name);
$objectToSave->ID;
}
}
Right now I am creating a new object cast as a type of product and then setting it equal to the object passed to the function. Is there a better way of accomplishing this task? Am I going about the wrong way?
EDITED FOR CLARITY - ADD FULL PRODCUTDAL CLASS
You don't need to cast the object, you can just use it as if it was a product.
$name = $objectToSave->Name;
I´m not sure what you are trying to achieve, but if $objectToSave is already of class product:
You can simply call $objectToSave->SaveItem() (assuming SaveItem() is part of the product class) and access it´s properties in the function like $this->Name, etc.;
In your code $productObject and $objectToSave will hold a reference to the same object.
Type casts in PHP are done like this:
$converted = (type) $from;
Note, that this won't work if the object types are not compatible (if for example $form happens to be a string or object of mismatching type).
But usual solution (called Active Record pattern, present for example in Zend Framework) is to have a base class for a database item called Row. Individual items (for example the class product from your sample) then inherit from this class.
Typical ZF scenario:
$table = new Product_Table();
$product = $table->find($productId); // load the product with $productId from DB
$product->someProperty = $newPropertyValue;
$product->Save(); // UPDATE the database
Which is IMO much better than your solution.
EDIT:
You can't cast between two unrelated objects, it is not possible.
If you want to use the DAL like this, skip the "product" object and go for simple associative array. You can enumerate over its members with foreach, unlike object's properties (you could use reflection, but that's overkill).
My recommendation: Go for the Active Record pattern (it is easy to implement with magic methods). It will save you a lot of trouble.
Currently, you are creating a new Product, then discarding it immediately (as its reference is replaced by $objectToSave.) You will need to copy its properties one by one, I regret.
foreach (get_object_vars($objectToSave) as $key => $value)
{
$product->$key = $value;
}
(If the properties of $objectToSave are private, you will need to a expose a method to_array() that calls get_object_vars($this).)
Lets say I have a class like this:
Class User {
var $id
var $name;
}
And I run a query using PDO in php like so:
$stm = $db->prepare('select * from users where id = :id');
$r = $stm->execute(array(':id' => $id));
$user = $r->fetchObject('User');
If I vardump my user object it has all kinds of other fields in it that I have not defined in the User class. Obviously I could make my query specific so that it only gives me back the fields I need/want. But if I don't want to do that is there any way to make this work the way I want it to?
I like the idea of fetchObject, because it's one line of code to create this object and set member variables for me. I just don't want it to set variables I haven't defined in my class.
EDIT:
Well it seems like karim79 is right and the fetch or fetchObject won't work the way I want it to. I've added the following bit of code after I do the fetch to get the desired results.
$valid_vars = get_class_vars('User');
foreach (get_object_vars($user) as $key => $value) {
if (!array_key_exists($key, $valid_vars)) {
unset($user->$key);
}
}
Obviously not the most elegant solution :/ I'm going to extend the PDOStatement class and add my own method fetchIntoObject or something like that and automatically do these unsets. Hopefully shouldn't be to much overhead, but I want to be able to easily fetch into an object with 1 line of code :)
SUPER EDIT:
Thanks to mamaar's comment I went back to the documentation again. I found what the problem is. http://us.php.net/manual/en/pdo.constants.php and scroll down to PDO::FETCH_CLASS and it explains that the magic method __set() is used if properties don't exist in the class. I overwrote the method in my target class and tada, works. Again, not the most elegant solution. But now I understand the WHY, and that's important to me :D
PDOStatement->execute() does not return an object - it returns TRUE/FALSE.
Change lines 2 and 3 to
if ( $stm->execute( array( ':id' => $id ) ) ){
$user = $stm->fetchObject( 'User' );
}
and it works
I don't think that's possible. fetchObject will create an instance of the classname specified as fetchObject's $class_name parameter (which defaults to stdClass). It will not check for existing classes with the same name and create an instance, assigning values only to member variables which match column names in the result. I would suggest relying on something more boring, like this:
$user = new User($result['id'], $result['name']);
Which would of course mean giving your User class a constructor:
Class User {
var $id
var $name;
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
}
You could probably use the PDOStatement->fetch method with PDO::FETCH_CLASS or PDO::FETCH_INTO as the $fetch_style parameter
Edit: So I've tried myself, and got it to work with PDOStatement->setFetchMode
class User
{
public $id;
public $name;
}
$db = new PDO('mysql:host=127.0.0.1;dbname=test', 'username', 'password');
$stmt = $db->prepare("select * from users where id=:userId");
$stmt->setFetchMode(PDO::FETCH_CLASS, 'User');
$stmt->execute(array(':userId' => 1));
$user = $stmt->fetch();
var_dump($user);
As alternative, you can of course just fetch an array and simply typecast this yourself:
$user = (User) $r->fetch();
Btw, I've not seen this behaviour. Maybe you have PDO::FETCH_LAZY activated, that might create extra data. You could test it with ->fetchObject("stdClass"), else the reason resides with your User class, or its Parent?