mysqli_fetch_object - variables as associative array - php

When using mysqli_fetch_object(), you can pass the name of an object as the second parameter. By doing so, mysqli creates the corresponding object and automatically sets the variables.
objectname {
// No defined properties
}
(...)
$object = mysqli_fetch_object($result, "objectname");
Under the assumption that $result contains data for "forname" and "surname", we would now have access to:
$object->forename;
$object->surname;
Is it possible to fetch the data into an associative array instead? Like shown here:
objectname {
public $data = array();
}
(...)
$object = mysqli_fetch_object($result, "objectname");
And then having:
$object->data["forename"];
$object->data["surname"];
If not: What is the code, MySQLi uses to populate the object with mysqli_fetch_object()? Considering that MySQLi is even able to change predefined private variables, it is a total mystery for me.

Unfortunately, you can't do it with mysqli. Usually, I would recommend PDO in such situations but even PDO doesn't have such capability.
There is a workaround though. You can create your own class with a constructor and a private property.
class MyClass
{
public function __construct(private array $data) {}
}
$stmt = $mysqli->prepare('SELECT id, name FROM users');
$stmt->execute();
$result = $stmt->get_result();
$data = [];
foreach ($result as $row) {
$data[] = new MyClass($row);
}
var_dump($data);
In the code above I am iterating over the result set and creating a new object passing the row each time as a constructor argument. You can control the visibility of the property using this approach.
P.S. If you are using PHP 7 then you can define the class this way:
class MyClass
{
private array $data;
public function __construct(array $data)
{
$this->data = $data;
}
}

Related

Query array or object property?

I'm still new to OOP and this is probably a simple question, not sure if I'm overthinking this.
Let's say we have a simple class like the following that we can use to instantiate an object that can generate an array:
class gen_arr {
public $arr = array();
public function fill_arr() {
$this->arr["key"] = "value";
}
}
// instantiate object from gen_arr
$obj = new gen_arr();
Now if you wanted to get the value of the object's array's item, would you generate an array first and then echo the value like:
$arr = $obj->fill_arr();
echo $arr["key"];
Or would you access the object's property directly?
echo $obj->arr["key"]
In the actual code the property is private and there is a method that allows the viewing of the property array, the above is just to simplify the question.
Are there performance considerations and/or just best practices when it comes to this kind of case?
UPDATE:
It's still unclear from the answers if the best way is to generate an array from the property and access that array or just access the property directly (through the getter method)
Since you are filling the array with items only on fill_arr, those items wont be availabl until you call $arr = $obj->fill_arr();.
If you want to directly call the array, then you have to fill this array on the constructor function of this call like this:
class gen_arr {
public $arr = array();
function __construct() {
$this->arr["key"] = "value";
}
}
First off, the class you shared with us has a range of problems:
its sole instance property is public and can be modified by anyone
you have some temporal coupling, the method fill_arr() needs to be invoked before accessing the the value makes any sense
Encapsulation
Reduce the visibility of the instance property from public to private, so that the property can only be modified by the object itself, and provide an accessor instead:
class gen_arr
{
private $arr;
public function fill_arr()
{
$this->arr["key"] = "value";
}
public function arr()
{
return $this->arr;
}
}
Temporal Coupling
Remove the method fill_arr() and instead initialize the property $arr in one of the following options:
initialize field lazily when accessed the first time
initialize field in the constructor
initialize field with a default value
initialize field with a value injected via constructor
Initialize field lazily when accessed the first time
Initialize the field when it's accessed the first time:
class gen_arr
{
private $arr;
public function arr()
{
if (null === $this->arr) {
$this->arr = [
'key' => 'value',
];
}
return $this->arr;
}
}
Initialize field in the constructor
Assign a value during construction:
class gen_arr
{
private $arr;
public function __construct()
{
$this->arr = [
'key' => 'value',
];
}
public function arr()
{
return $this->arr;
}
}
Initialize field with a default value
Assign a value to the field directly, which works fine if you don't need to do any computation:
class gen_arr
{
private $arr = [
'key' => 'value',
];
public function arr()
{
return $this->arr;
}
}
Initialize field with a value injected via constructor
If the values are not hard-coded or otherwise calculated (as in the previous examples), and you need to be able to instantiate objects with different values, inject values via constructor:
class gen_arr
{
private $arr;
public function __construct(array $arr)
{
$this->arr = $arr;
}
public function arr()
{
return $this->arr;
}
}
Accessing and dereferencing values
This seems like this is your actual question, so the answer is - of course - It depends!.
Let's assume we have provided an accessor instead of accessing the otherwise public field directly:
Since PHP 5.4, the following is possible:
$object = new gen_arr();
echo $object->arr()['key'];
If you are still using an older version of PHP, you obviously can't do that and have to do something like this instead:
$object = new gen_arr();
$arr = $object->arr();
echo $arr['key'];
Largely, though, the answer to this question depends on the circumstances, and what you want to achieve. After all, readability is key for maintenance, so it might just make sense for you to introduce an explaining variable.
Note About your example, you could just use an ArrayObject instead:
$arr = new \ArrayObject([
'key' => 'value',
]);
echo $arr['key']);
For reference, see:
http://wiki.c2.com/?EncapsulationDefinition
http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/
http://php.net/manual/en/language.oop5.properties.php
http://wiki.c2.com/?ItDepends
http://php.net/manual/en/migration54.new-features.php
https://refactoring.com/catalog/extractVariable.html
http://wiki.c2.com/?IntroduceExplainingVariable
http://php.net/manual/en/class.arrayobject.php
For an example, see:
https://3v4l.org/qVVBM
First fill up the array
$gen_arr = new gen_arr();
$gen_arr->fill_arr();
then get the values with a getter method
$val = $gen_arr->getValue($key);
A getter method would be like this
public function getValue($key) {
return $this->arr[$key];
}
And certailny make the $arr property private

PHP type declaration (typehint) in function

As I understand, there is no way (even in PHP 7) to force function to take typehint of its parameter as an array of objects.
I think there is a workaroud to do that by defining another object as Traversable, which would be the container for all MyObject that would be otherwise in the array and setting the typehint to that Traversable.
But it would be so cool, if I could do this:
public function foo(MyObject[] $param) {}
So my question is, is there any reason why PHP doesn't implement this?
you can also
$arrYourObjectType = new YourObjectType[];
Then if the object array is your return type for the function, in your phpdoc, to type hint the return value, in the phpdoc above your function:
/**
* #param $whatever
* #return array ...$arrYourObjectType
**/
public function someFunction($whatever){
$arrYourObjectType[] = new YourObjectType[];
$x=0;
foreach($arrValues as $value)
{
$objYourObjectType = new YourObjectType();
$objYourObjectType->setSomething($value[0])
->setSomethingElse($value[1]);
(and so on)
//we had to set the first element to a new YourObjectType so the return
//value would match the hinted return type so we need to track the
//index
$arrYourObjectType[$x] = $objYourObjectType;
$x++;
}
return $arrYourObjectType;
}
Then in IDE's such as php storm, when using a class containing that function the return value of the function will be treated like an array of your object (properly hinted) and the IDE will expose the object methods on each element of the object array properly.
You can do things easy/dirty without all this, but phpStorm won't hint the methods on the elements of your object array properly.
If feeding an array of YourObjectType to a function...
/**
*#param YourObjectType ...$arrYourObjectType
**/
public function someFunction(YourObjectType...$arrYourObjectType){
foreach($arrYourObjectType as $objYourObject)
{
$someval = $objYourObject->getSomething();//will be properly hinted in your ide
}
}
It's all about the ellipses when feeding and retrieving object arrays :-)
Edit: I had a few things wrong with this because I did it from memory... corrected... Sorry bout this...
I don't understand well your question but if you want to insert data inside a object you can do:
<?php
class Insert
{
public $myobject = array();
public function foo($insert_in_object, $other_param_in_object) {
$this->myobject[] = $insert_in_object;
$this->myobject[] = $other_param_in_object;
return $this->myobject;
}
}
$start = new Insert();
$myobject = $start->foo('dog', 'cat');
var_dump($myobject)
?>

PHP An iterator cannot be used with foreach by reference

I have an object that implements Iterator and holds 2 arrays: "entries" and "pages". Whenever I loop through this object, I want to modify the entries array but I get the error An iterator cannot be used with foreach by reference which I see started in PHP 5.2.
My question is, how can I use the Iterator class to change the value of the looped object while using foreach on it?
My code:
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
//...Iterator methods...
}
//looping
//throws error here
foreach ($flavors as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
The reason for this is that sometimes $flavors will not be an instance of my class and instead will just be a simple array. I want to be able to modify this array easily regardless of the type it is.
I just tried creating an iterator which used:
public function &current() {
$element = &$this->array[$this->position];
return $element;
}
But that still did not work.
The best I can recommend is that you implement \ArrayAccess, which will allow you to do this:
foreach ($flavors as $key => $flavor) {
$flavors[$key] = $flavor->stdClassForApi();
}
Using generators:
Updating based on Marks comment on generators, the following will allow you to iterate over the results without needing to implement \Iterator or \ArrayAccess.
class PaginatedResultSet {
public $entries = array();
public function &iterate()
{
foreach ($this->entries as &$v) {
yield $v;
}
}
}
$flavors = new PaginatedResultSet(/* args */);
foreach ($flavors->iterate() as &$flavor) {
$flavor = $flavor->stdClassForApi();
}
This is a feature available in PHP 5.5.
Expanding upon Flosculus' solution, if you don't want to reference the key each time you use the iterated variable, you can assign a reference to it to a new variable in the first line of your foreach.
foreach ($flavors as $key => $f) {
$flavor = &$flavors[$key];
$flavor = $flavor->stdClassForApi();
}
This is functionally identical to using the key on the base object, but helps keep code tidy, and variable names short... If you're into that kind of thing.
If you implemented the iterator functions in your calss, I would suggest to add another method to the class "setCurrent()":
//$flavors = instance of this class:
class PaginatedResultSet implements \Iterator {
private $position = 0;
public $entries = array();
public $pages = array();
/* --- Iterator methods block --- */
private $current;
public function setCurrent($value){
$this->current = $value;
}
public function current(){
return $this->current;
}
//...Other Iterator methods...
}
Then you can just use this function inside the foreach loop:
foreach ($flavors as $flavor) {
$newFlavor = makeNewFlavorFromOldOne($flavor)
$flavors -> setCurrent($newFlavor);
}
If you need this function in other classes, you can also define a new iterator and extend the Iterator interface to contain setCurrent()

How to use mysqli::fetch_object()?

How do people usually turn mysqli results into objects?
I cannot find any examples with fetch_object() when using custom classes. Assume the following setup
class Test1 {
function __construct(array $data) { ... }
}
class Test2 {
function __construct(array $data) { ... }
}
Assume that I have multiple classes with the same constructors, how can I dynamically instantiate these classes?
I would imagine something like this:
function customQuery($query, $class) {
// Get some data by mysqli query, assume we get the data as $result object
...
// Now I would like to store the data in an array with instances of $class
$array = array();
while ($obj = $result->fetch_object($class, ???) {
$array[] = $obj;
}
// Return the array with instances of $class
return $array;
}
What do I use as arguments there for my question marks? I only know that both class constructors of Test1 and Test2 want an associative array as input (probably not the same length!).
In the end I simply want to do something like
$arr1 = customQuery('SELECT id, product FROM test1 LIMIT 10', 'Test1');
$arr2 = customQuery('SELECT id, name, address FROM test2 LIMIT 10', 'Test2');
Of course I would appreciate your input if you have better ideas to achieve my goal.
Take a look at "fetch_object'
You have a argument $class_name
From the docs:
The name of the class to instantiate, set the properties of and return. If not specified, a stdClass object is returned.
It will automaticly create an instance of the given class and it will set the properties. so no need to pass an array
http://www.php.net/manual/en/mysqli-result.fetch-object.php
A bit more explanation,
The fetch_object just fills the private properties etc (PHP Magic... i don't like it).
If your object has required parameters in the constructor but you want to fetch it from the database the fetch_object lets you define arguments for the constructor so it can be constructed instead of throwing warnings/errors.
class Test {
private $title;
public function __construct($required) {
}
}
$mysql->fetch_object('Test'); //will trigger errors/warnings
$mysql->fetch_object('Test', array(null)); //won't
What you could do is simpel fetch an array and construct the object
function query($query, $class) {
$data = $query->fetch_assoc();
$instance = new $class($data);
return $instance;
}
The fetch_object() method will return an instance of stdClass. If you specify a class name, however, it'll return an instance of that class and populate attributes based on the data returned. You do NOT need to specify all of your columns in the class unless you want to change their visibility (i.e. protected or private). Otherwise they'll default to public.
Also, it's a good idea to have a static method within the class you want to instantiate to separate concerns neatly. See:
class Class_Name
{
public static function customQuery($query)
{
$return = array();
if ($result = $mysqli->query($query)) {
// not passing a class name to fetch_object()
// will return an instance of stdClass
while ($obj = $result->fetch_object('Class_Name')) {
// add the fetched object (as an instance of the
// 'Class_Name' class in this case) to the return array.
$return[] = $result;
}
return $return;
}
// return false if no results found
return false;
}
}
Call the static method thus and you'll get an array of 'Class_Name' objects:
$results = Class_Name::customQuery($query);
If you're only querying for 1 result the above would be like this:
class Class_Name
{
public static function customQuery($query)
{
$return = array();
if ($result = $mysqli->query($query)) {
return $result->fetch_object('Class_name');
}
// return false if no results found
return false;
}
}
With this you'll get a single 'Class_Name' object.
Note, if you're using a namespaced class apply the fully qualified namespaced class name.
There is not much use for this function.
Better make your object's constructor to accept an array with settings and use like this
while ($row = $result->fetch_assoc($res) {
$array[] = new Foo($row);
}
also note that fetch_assoc/fetch_object isn't always available with prepared statements.
You use fetch_object on a result. You do while ($obj = $result->fetch_query()) { ... }
function customQuery($query) {
if ($result = $mysqli->query($query)) {
while ($obj = $result->fetch_object()) {
# do stuff.
}
} else {
# raise some error, query did not make it.
}
}
If you specify param, this is a class that will be instantiated to handle the results.
# $result->fetch_object('MyClass');
MyClass {
private $id;
public function __construct($id/* fields */) {
$this->id = $id; # say `id` is the only field
}
}
Here is the proper way to use it:
<?php
$res = $mysqli->query($q);
while($params = $res->fetch_assoc()):
endwhile;
$res->free();
$res = $mysqli->query($q);
while ($obj = $res->fetch_object('ClassName', array($params)):
$obj_list[] = $obj;
endwhile;
?>
mysqli::fetch_object() doesn't send the fields names and values to the constructor, it just creates an object with the attributes without passing by the setters. If you want to pass by the constructor, you must first recover the result and give it to him in an array in parameters.
So what's happening if you want to pass by the constructor? At first, fetch_object() will create the attributes then it will overwrite them passing by the constructor.

Variablename as $this-> value?

I am currently in the development of my Class in PHP.
I have an array with values in it, and I would like to use the array fieldname as a $this reference. Let me show you what I got:
<?php
class Server {
private $playlist;
private $mp3;
private static $ressourceFolder;
private static $sudoUser;
And in my array it contains:
array(6) {
["playlist"]=>
int(8002)
["mp3"]=>
int(1024)
["ressourceFolder"]=>
bool(true)
["sudoUser"]=>
bool(true)
}
So I would like to use in my foreach something to get the value of the array field into the class global variable, the array fieldname is the same as the variable so this 'should' work, but it doesn't :(
foreach($ressourceArray as $ressourceField=>$ressourceValue) {
$this->$ressourceField = $ressourceValue;
}
I would really appreciate if someone could tell me why this can't work and how to make this 'workable'...
Thanks in advance!
It does work, see Demo:
<?php
$array = array("playlist"=> 8002, "mp3"=>1024);
class Mix {
public function __construct($array) {
foreach($array as $key => $value) {
$this->$key = $value;
}
}
}
$class = new Mix($array);
var_dump($class);
It will assign new public members to the object $this based on key/value pairs of the array.
Dealing with bad named keys in the array
If the keys contain values that are not valid variable names, it can be non-trivial to access the properties later ({property-name}), see PHP curly brace syntax for member variable.
Casting the array to object before adding will help to prevent fatal errors for those key names that are completely invalid:
$object = (object) $array;
# iterate over object instead of array:
foreach($object as $key => $value) {
$this->$key = $value;
}
Those keys are just dropped by the cast.
You may get it working with the magic method __set and __get. See: http://php.net/manual/en/language.oop5.magic.php
And in my array it contains:
What array? That looks like a array dump of an instance of the class.
into the class global variable
What global class variable? Classes are not variables. Variables may hold references to objects, or class names.
Assuming you want to iterate through the properties of an object, and
$ressourceArray = new Server();
The code will work as expected.
If the loop is within a class method, then the loop should be....
foreach($this as $ressourceField=>$ressourceValue) {
$this->$ressourceField = $ressourceValue;
}
If you mean that you are trying to initialize the object properties from an array...
class Server {
...
function setValues($ressourceArray)
{
foreach($ressourceArray as $ressourceField=>$ressourceValue) {
$this->$ressourceField = $ressourceValue;
}
}
(BTW there's only one 's' in 'resource')

Categories