dynamically retrieve object method - php

I try to dynamically retrieve a method of a class but php throws an exception which says Undefined property: stdClass ...
and How i try to get the values
private function getExactValue($row, $name)
{
$tempRow = clone $row;
foreach( explode('->', $name) as $key => $value)
{
$temp = $tempRow->{$value};
unset($tempRow);
$tempRow = $temp;
}
return $tempRow;
}
$row is an instance of an Object (not Std one)
$name is what i need in the Object to traverse , for example when i need $row->student->gifts->totalPoint() just pass the student->gifts->totalPoint() to the method for $name parameter
can you tell me what my mistake is?

I see what you are trying to do here. My first word of advice is that you are going about what you are trying to achieve in a very hackish way. If you wanted a better way to be able to execute arbitrary methods on an unknown object, I would suggest you look into PHP's reflection capabilities.
That being said, the problem with your code would appear to be that you are trying to execute a method via string, where what you need to do is utilize the method's name. What I would suggest is that within your loop where you explode the string on ->, you try to detect if it is a method or not, and then act accordingly. That could look like this:
foreach( explode('->', $name) as $value)
{
$value_trimmed = rtrim($value, '()');
if ($value === $value_trimmed) {
// this is a property
$tempRow = $tempRow->{$value};
} else {
// this is a method
$tempRow = $tempRow->{$value_trimmed}();
}
}
You should probably also do some validation on the input as well to make sure you have valid property/method names for each segment, as well as add validation that the entire string is indeed properly formed (i.e. you don't have things like foo->->bar(())). Of course this make no mention of how to handle array like foo[0]->bar() which you might also need to accommodate.

Related

How can I check if a variable is "loopable"?

I got a method that receives a parameter that could be any number of types.
It could be an array.
It could be an object that is in some way iterable such as a collection.
It could be something else altogether such as a string or integer which would throw
Warning: Invalid argument supplied for foreach()
How can I reliably check that the variable is "loopable" to avoid the warning?
I've tried is_array() as below but that only works for arrays:
if(is_array($mystery_type)){
foreach($mystery_type as $value){
...
}
}
I was surprised to not find an answer to this here, which probably means it's very simple and I'm missing something obvious.
If you are using PHP 7.1+ you can use is_iterable():
if (is_iterable($mystery_type)) {
// your loop
}
Or its polyfill (found in the documentation comments):
if (!function_exists('is_iterable')) {
function is_iterable($obj) {
return is_array($obj) || (is_object($obj) && ($obj instanceof \Traversable));
}
}
Read more: iterable pseudo-type (PHP 7.1+)
The best way is to not write methods that accept multiple types of data as input but to write smaller specific methods for each type of input. The code of such smaller methods is shorter, simpler, easier to read and understand (and to test).
The method for iterable data structures should use the Traversable interface as the type of its argument:
public function f(Traversable $input) {
// This foreach is guaranteed to always work
foreach ($input as $key => $value) {
// Do something with $value and/or $key
}
}
PHP triggers an error when method f() is invoked with a value that does not implement the Traversable interface.

How to concatenate variable in drupal

I want to know how can I concatenate [und][0][value].
I don't want to write every time [und][0][value]. So I have do like this:
<?php
$und_value = $load->field_testimonial_location['und'][0]['value'];
$query = db_select('node','n');
$query->fields('n',array('nid'));
$query->condition('n.type','testimonial','=');
$result = $testimonial_query->execute();
while($fetch = $result->fetchObject()){
$load = node_load($fetch->nid);
// $location = $load->field_testimonial_location['und'][0]['value'];
$location = $load->field_testimonial_location.$und_value;
echo $location;
}
But its not working. It outputs Array Array So have any idia for this problem? How can I do? Full code here
Why don't you make some function which will take node field as parameter and return it's value
function field_value($field){
return $field['und'][0]['value'];
}
Something like that (not tested).
But if you don't want to use function try using curly braces like:
$location = $load->{field_testimonial_location.$und_value};
That should work...
Extending answer posted by MilanG, to make function more generic
function field_value($field, $index = 0 ){
return $field['und'][$index]['value'];
}
There are time when you have multi value fields, in that case you have to pass index of the value also. For example
$field['und'][3]['value'];
Please do not use such abbreviations, they will not suit all cases and eventually break your code.
Instead, there is already a tool do create custom code with easier syntax: Entity Metadata Wrapper.
Basically, instead of
$node = node_load($nid);
$field_value = $node->field_name['und'][0]['value'];
you can then do something like
$node = node_load($nid);
$node_wrapper = entity_metadata_wrapper('node', $node);
$field_value = $node_wrapper->field_name->value();
With the node wrapper you can also set values of a node, it's way easier and even works in multilingual environments, no need to get the language first ($node->language) or use constants (LANGUAGE_NONE).
In my custom module, I often use $node for the node object and $enode for the wrapper object. It's equally short and still know which object I am working on.

Get variable name from within the called function

Can I do this, maybe using ReflectionClass ?
myprintr($some_object);
function myprintr(){
foreach(func_get_args() as $key => $arg){
// here I want to get the name of the passed variable, "some_object"
// $key seems to be numeric...
}
}
You cannot get the name of the "variable", as there is no variable.
eg:
myprintr("test");
myprintr(myotherfun());
Note: I'm not sure what you are trying to do, but I just feels terrifyingly wrong.. the whole point of functions and objects is to create barriers, and it shouldn't matter what is in the caller's context..
If the user passes an object to myprintr(), then you can use
if (is_object($arg)) {
$className = get_class($arg);
}
to get the name of the object type that has been passed, which you can then feed to reflection
but the reflection constructor will accept either a class name or an object as an argument, so you don't even need the class name to instantiate a reflection class
EDIT
Just for the sake of playing a bit with this concept (and not creating any dependency on globals), or on whether the arguments are variables, values returned from functions, strings, etc:
class Test{};
function myTest() {
$some_object = new Test();
myprintr($some_object);
}
function myprintr(){
$callStack = debug_backtrace();
$calledAt = $callStack[0];
$callingFile = file($calledAt['file'],FILE_IGNORE_NEW_LINES);
$callingLine = $callingFile[$calledAt['line']-1];
$callingLine = substr($callingLine,strpos($callingLine,__METHOD__));
$calledWithArgNames = trim(substr($matches[0],1,-1));
var_dump($calledWithArgNames);
$args = func_get_args();
foreach($args as $arg) {
var_dump($arg);
}
}
myTest();
$some_object = new Test();
$some_other_object = &$some_object;
$t = 2;
$gazebo = "summer house";
$visigoth = pi() / 2; myprintr($some_other_object,pi(), atan2(pi(),$t), $visigoth, "Hello $t World", $gazebo); $t = log($t/$visigoth);
This retrieves all the arguments passed by the calling function in $calledWithArgNames, so for the first call you have:
'$some_object'
and for the second call:
'$some_other_object,pi(), atan2(pi(),$t), $visigoth, "Hello $t World", $gazebo'
This still requires splitting down into the individual arguments (a preg_split on commas, except where they're inside braces), but is certainly a step closer to what you're actually asking for.
You can't access argument names that don't exist: myprintr doesn't specify any variable names, and func_get_args() will only ever return a numerically indexed array.
I suppose you could add docblock comments and access them with reflection, but this seems like an extraordinary amount of overhead for functionality that you most likely don't need anyway. Using reflection on the function's arguments itself won't do anything for you because, again, you didn't specify any arguments in the function's argument signature.
PHP function arguments are ordered. They aren't something you can reference like an associative array. If you want access to "associative" type key names for a function or method's arguments, you'll have to specify an array argument and pass a value with the associative keys you want, like this:
myfunc(array $args=[])
{
$key1 = isset($args['key1']) ? $args['key1'] : NULL;
$key2 = isset($args['key2']) ? $args['key2'] : NULL;
}
If it is an object you can use func_get_args() and spl_object_hash() to identify the object and then search it in $GLOBALS. There you find the name.
class Test{};
$some_object = new Test();
myprintr($some_object);
function myprintr(){
$args = func_get_args();
$id = spl_object_hash($args[0]);
foreach($GLOBALS as $name => $value)
{
if (is_object($value) && spl_object_hash($value) == $id) echo $name;
}
}
http://codepad.org/gLAmI511

PHP Fatal Error: "Can't use method return value in write context in.... "

Trying to use a class that expects something like:
$client->firstname = 'bob';
$client->lastname = 'jones';
So I want to pass this data to the script in an array... where the keys and values are set elsewhere. I want to step through the array passing the key and value to the class. Trying to use this:
while($Val = current($CreateClientData)){
$client->key($CreateClientData) = $Val;
next($CreateClientData);
}
getting this:
Fatal error: Can't use method return value in write context in
blahblahpath on line 40.
Line 40 being: $client->key($CreateClientData) = $Val;
How can I do this?
If $client is already an instance of some class, and $CreateClientData is an array, then you probably wan to do something like this:
foreach($CreateClientData as $k => $v) {
$client->{$k} = $v;
}
This assumes of course that every key in the array is a valid member of the $client instance. If not, then you will have to do some additional checking before assigning the value, or you will have to wrap the assignment in a try / catch.
EDIT
The answer as to why your code doesn't work is because PHP doesn't allow for assignment of class properties to certain functions that return values. In your case, key($CreateClientData) returns a key. So you could alter your code and just add
$key = key($CreateClientData);
$client->$key = $Val;
But, the foreach loop is a lot cleaner anyway.
Why don't you use a foreach loop?
foreach($CreateClientData as $key => $val) {
$client->$key = $val;
}

How to Pass Class Variables in a Function Parameter

The main function of the example class uses the reusableFunction twice with different data and attempts to send that data to a different instance variable ($this->result1container and $this->result2container) in each case, but the data doesn't get into the instance variables.
I could get it to work by making reusableFunction into two different functions, one with array_push($this->result1container, $resultdata) and the other with array_push($this->result2container, $resultdata), but I am trying to find a solution that doesn't require me to duplicate the code.
My solution was to try to pass the name of the result container into the function, but no go. Does somebody know a way I could get this to work?
Example Code:
Class Example {
private $result1container = array();
private $result2container = array();
function __construct() {
;
}
function main($data1, $data2) {
$this->reusableFunction($data1, $this->result1container);
$this->reusableFunction($data2, $this->result2container);
}
function reusableFunction($data, $resultcontainer) {
$resultdata = $data + 17;
// PROBLEM HERE - $resultcontainer is apparently not equal to
// $this->result1container or $this->result2container when I
// try to pass them in through the parameter.
array_push($resultcontainer, $resultdata);
}
function getResults() {
return array(
"Container 1" => $this->result1container,
"Container 2" => $this->result2container);
}
}
(If this is a duplicate of a question, I apologize and will happily learn the answer from that question if somebody would be kind enough to point me there. My research didn't turn up any answers, but this might just be because I didn't know the right question to be searching for)
It looks to me like you want to be passing by reference:
function reusableFunction($data, &$resultcontainer) {
...
If you don't pass by reference with the & then you are just making a local copy of the variable inside reuseableFunction .
You are changing the copy, not the original. Alias the original Array by referenceDocs:
function reusableFunction($data, &$resultcontainer) {
# ^
And that should do the job. Alternatively, return the changed Array and assign it to the object member it belongs to (as for re-useability and to keep things apart if the real functionality is doing merely the push only).
Additionally
array_push($resultcontainer, $resultdata);
can be written as
$resultcontainer[] = $resultdata;
But that's just really FYI.
You may pass the attributes name as a String to the method like this:
function reusableFunction($data, $resultcontainer) {
$resultdata = $data + 17;
array_push($this->{$resultcontainer}, $resultdata);
}
//..somewhere else..
$this->reusableFunction($data, 'result2Container')
Some php experts wrote some texts about "why you shouldn't use byReference in php".
Another solution would be to define the containers as an array. Then you can pass an "key" to the method that is used to store the result in the array. Like this:
private $results = array();
function reusableFunction($data, $resIdx) {
$resultdata = $data + 17;
array_push($this->$results[$resIdx], $resultdata);
}
//..somewhere else..
$this->reusableFunction($data, 'result2Container');
//..or pass a number as index..
$this->reusableFunction($data, 1);

Categories