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
}
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.
This question already has an answer here:
How to call the constructor with call_user_func_array in PHP
(1 answer)
Closed 7 years ago.
I have searched many a page of Google results as well as here on stackoverflow but cannot find a solution that seems to fit my situation. I appear to have but one last snag in the function I am trying to build, which uses call_user_func_array to dynamically create objects.
The catchable fatal error I am getting is Object of class Product could not be converted to string. When the error occurs, in the log I get five of these (one for each argument): PHP Warning: Missing argument 1 for Product::__construct(), before the catchable fatal error.
This is the code of the function:
public static function SelectAll($class, $table, $sort_field, $sort_order = "ASC")
{
/* First, the function performs a MySQL query using the provided arguments. */
$query = "SELECT * FROM " .$table. " ORDER BY " .$sort_field. " " .$sort_order;
$result = mysql_query($query);
/* Next, the function dynamically gathers the appropriate number and names of properties. */
$num_fields = mysql_num_fields($result);
for($i=0; $i < ($num_fields); $i++)
{
$fetch = mysql_fetch_field($result, $i);
$properties[$i] = $fetch->name;
}
/* Finally, the function produces and returns an array of constructed objects.*/
while($row = mysql_fetch_assoc($result))
{
for($i=0; $i < ($num_fields); $i++)
{
$args[$i] = $row[$properties[$i]];
}
$array[] = call_user_func_array (new $class, $args);
}
return $array;
}
Now, if I comment out the call_user_func_array line and replace it with this:
$array[] = new $class($args[0],$args[1],$args[2],$args[3],$args[4]);
The page loads as it should, and populates the table I am building. So everything is absolutely functional until I try to actually use my $args array within call_user_func_array.
Is there some subtle detail about calling that array that I am missing? I read the PHP manual for call_user_func_array once, and then some, and examples on that page seemed to show people just building an array and calling it for the second argument. What could I be doing wrong?
You can't call the constructor of $class like this:
call_user_func_array (new $class, $args);
That's no valid callback as first parameter. Let's pick this apart:
call_user_func_array (new $class, $args);
Is the same as
$obj = new $class;
call_user_func_array ($obj, $args);
As you can see, the constructor of $class has been already called before call_user_func_array comes into action. As it has no parameters, you see this error message:
Missing argument 1 for Product::__construct()
Next to that, $obj is of type object. A valid callback must be either a string or an array (or exceptionally a very special object: Closure, but that's out of discussion here, I only name it for completeness).
As $obj is an object and not a valid callback, so you see the PHP error message:
Object of class Product could not be converted to string.
PHP tries to convert the object to string, which it does not allow.
So as you can see, you can't easily create a callback for a constructor, as the object yet not exists. Perhaps that's why you were not able to look it up in the manual easily.
Constructors need some special dealing here: If you need to pass variable arguments to a class constructor of a not-yet initialize object, you can use the ReflectionClass to do this:
$ref = new ReflectionClass($class);
$new = $ref->newInstanceArgs($args);
See ReflectionClass::newInstanceArgs
Not possible using call_user_func_array(), because (as the name suggest) it calls functions/methods, but is not intended to create objects, Use ReflectionClass
$refClass = new ReflectionClass($class);
$object = $refClass->newInstanceArgs($args);
Another (more design-based) solution is a static factory method
class MyClass () {
public static function create ($args) {
return new self($args[0],$args[1],$args[2],$args[3],$args[4]);
}
}
and then just
$object = $class::create($args);
In my eyes it's cleaner, because less magic and more control
I use this for singleton factory pattern, becouse the ReflectionClass brokes the dependence tree, I hate the use of eval but its the only way to i find to simplificate the use of singleton pattern to inject mockObjects whith PHPUnit whitout open the class methods to that injection, BE CAREFULL WHITH THE DATA WHAT YOU PASS TO eval FUNCTION!!!!!!!! YOU MUST BE SURE THAT IS CLEANED AND FILTERED!!!
abstract class Singleton{
private static $instance=array();//collection of singleton objects instances
protected function __construct(){}//to allow call to extended constructor only from dependence tree
private function __clone(){}//to disallow duplicate
private function __wakeup(){}//comment this if you want to mock the object whith php unit jejeje
//AND HERE WE GO!!!
public static function getInstance(){
$a=get_called_class();
if(!array_key_exists($a, self::$instance)){
if(func_num_args()){
/**HERE IS THE CODE **//
$args=func_get_args();
$str='self::$instance[$a]=new $a(';
for($i=0;$i<count($args);$i++){
$str.=(($i)?",":"").'$args['.$i.']';
}
eval($str.");");//DANGER, BE CAREFULLY...we only use this code to inject MockObjects in testing...to another use you will use a normal method to configure the SingletonObject
/*--------------------------*/
}else{
self::$instance[$a]=new $a();
}
}
return self::$instance[$a];
}
}
And to use that:
class MyClass extends Singleton{
protected function __construct(MyDependInjection $injection){
//here i use the args like a normal class but the method IS PROTECTED!!!
}
}
to instanciate the object:
$myVar= MyClass::getInstance($objetFromClassMyDependInjection);
it calls the constructor whith the args I pased. i know that i can get the same result extending the static method getInstance but to teamworking its more easy to use this way
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).)
I have a search class that I am using to fetch results from two different sources and combine them together. The Search class is the parent and has two children A and B which extend Search.
In the Search class, I have a method called fetch() which instantiates the two child objects to get their results. It looks something like this:
public function fetch(){
$a = new A($this);
$a_results = $a->fetch();
$b = new B($this);
$b_results = $b->fetch();
// code to combine the results here
}
The constructor of class A and B both look like this:
class A extends Search
{
public function __construct(Search $search){
parent::__construct($search->category, $search->offset, $search->keywords...);
}
It feels like I'm doing something wrong in that I'm passing a parent object to a child and then creating another parent object with the exact same data. Is there a better way to set this up?
I have it set this way because some parts of my application need to access class A and B directly, rather than through the parent Search class.
Use composition, for example have the Search class to have an array of sources, where each source is an instance of a Source class where you define what's common to a source and pass the parameters for each A and B sources.
The idea here, in case it's not clear, is for the Source class to return the data from the sources and let the Search class do the search. How practical or efficient this is depends on the actual source and way of searching
class Search {
private $sources = array();
public Search($p1,$p2,$p3,$p4) {
//Use proper parameters to define the sources
$sources[] = new Source("A",$p1,$p2,$p3,$p4);
$sources[] = new Source("B",$p1,$p2,$p3,$p4);
}
public function fetch() {
foreach ($source in $sources) {
$results[] = $source->fetch();
}
combine($results);
}
}
class Source {
//Whatever you need to define the source
public function fetch() {
//Fetch from the proper source
}
public Source($name,$p1,$p2,$p3,$p4) {
//Store the parameters to be able to operate
}
}