How do I access an attribute of an object by name, if I compute the name at runtime?
For instance. I loop over keys and want to get each value of the attributes "field_" . $key.
In python there is getattribute(myobject, attrname).
It works, of course, with eval("$val=$myobject->".$myattr.";");
but IMO this is ugly - is there a cleaner way to do it?
Keep always in mind that a very powerful feature of PHP is its Variable Variables
You can use
$attr = 'field' . $key;
$myobject->$attr;
or more concisely, using curl brackets
$myobject->{'field_'.$key};
$myobject->{'field_'.$key}
$val = $myobject->$myattr;
With reflection:
$reflectedObject = new ReflectionObject($myobject);
$reflectedProperty = $reflectedObject->getProperty($attrName);
$value = $reflectedProperty->getValue($myobject);
This works only if the accessed property is public, if it's protected or private an exception will occurr.
I know this is an old subject, but why not just use magic methods?
$myObj->__get($myAttr)
Related
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.
It know it can be done with get_class($variable).
The problem is that my $object is actually a string containing the variable name.
so:
$object = new MyClass();
$var = '$object';
$class = get_class($var); // obviously fails
I can't use get_class($object), because I don't have direct access to that variable (I'm producing the $var string from parsing a PHP expression using token_get_all())
I tried using eval(sprintf('return get_class(%s);', $var)), but it doesn't work because the variable appear undefined from eval's scope :(
Is there a way to do this?
I need to know the class in order to pass it to ReflectionMethod, so I can get information about a method (the next element in the PHP expression).
NVM: I'm pretty sure it is not possible. Sorry for asking:)
you can do
$var = new $object();
Try using variable variables: http://php.net/manual/en/language.variables.variable.php
Something like:
$var = 'object';
$class = get_class( $$var );
you can do the following
$ref = ltrim($var, '$');
get_class($ref);
The problem is easy to explain. What I want to do is to create an object, parameters and function calls based on $_POST variables.
$obj = new $_POST['object']['name']($_POST['object']['params']);
return json_encode(
$obj->$_POST['function']['name']($_POST['function']['params'])
);
The only problem here is that it gives the parameters as an array. And I want it to seperate the variables with a comma, like: $obj->function($var1 , $var2, $var3);. How can I achieve this?
This can be done, but you should only do it using a white list of possible objects to create. Otherwise it is kind of dangerous. A system this open may not be a good idea. If you can rethink the problem it may help to approach it a different way.
To solve your problem though, you can use a construct like this:
// Possible values:
$acceptable_classes = array('myClass1', 'myClass2', 'myClass3');
// If it is an allowed class,
if (in_array($_POST['object']['name'], $acceptable_classes) {
// Store it in a variable.
$class = $_POST['object']['name'];
$obj = new $class;
}
You need to keep a similar whitelist for the function. You might use a multidimensional array for that. You may extend this to hold acceptable parameters for the possible functions. It can blow out of proportion pretty quickly, but will help maintain security.
$acceptable_classes = array(
'myClass1' => array(
'func1', 'func2'
),
'myClass2' => array(
'func3', 'func4'
)
);
if ($acceptable_classes, array_key_exists($_POST['object']['name']) {
if (in_array($_POST['function']['name'], $acceptable_classes($_POST['object']['name'])) {
// Store it in a variable.
$class = $_POST['object']['name'];
$obj = new $class;
$func = $_POST['function']['name'];
return json_encode($obj->$func($_POST['function']['params']));
}
}
Do something like this:
call_user_func_array( array($obj, $_POST['function']['name']), $_POST['function']['params'])
Documentation of the call_user_func_array function
call_user_func_array() is what you're looking for. On a side note, running $_POST values is a security risk, I hope you sanitize them properly.
The best way is to create object without arguments passed to constructor and initiate it using some other method. For example:
$obj = new $_POST['object']['name']();
$result = call_user_func_array(array($obj,'init'),$_POST['object']['params']);
Also, in PHP 5.4+ you can use this "hack":
$reflection = new ReflectionClass($_POST['object']['name']);
$obj = $reflection->newInstanceWithoutConstructor();
call_user_func_array(array($obj,'__constructor'),$_POST['object']['params']);
But I advise you to change your architecture so you would be able to pass array to constructor "as is".
have the function take an array
$name = $_POST['object']['name'];
$obj = new $name;
$array = explode(',', $_POST['object']['params'];
$obj->function($array);
It it possible to call a class and method from strings?
Something like:
// $_REQUEST['var'] = 'House-Kitchen';
$var = explode('-',$_REQUEST['var']);
echo $var[0]->$var[1];
Yes. its possible. If $var[0] is the name of class then following will work.
call_user_func(array($var[0], $var[1]));
If $var[0] is the name of class instance then following will work.
call_user_func(array(get_class(${$var[0]}), $var[1]));
Links:
call_user_func
get_class
The better way is to try.
I did try :
$House = new stdClass();
$House->Kitchen = "visible result";
$_REQUEST['var'] = 'House-Kitchen';
$var = explode('-',$_REQUEST['var']);
echo $$var[0]->$var[1];
It works. Be careful : you need to use double $ for the first element (to use variable with $var[0] name).
And be very careful : it's a high security breach (you allow everyone to call methods on current defined class).
Anyone has an idea if this is at all possible with PHP?
function foo($var) {
// the code here should output the value of the variable
// and the name the variable has when calling this function
}
$hello = "World";
foo($hello);
Would give me this output
varName = $hello
varValue = World
EDIT
Since most people here 'accuse' me of bad practices and global variables stuff i'm going to elaborate a little further on why we are looking for this behaviour.
the reason we are looking at this kind of behaviour is that we want to make assigning variables to our Views easier.
Most of the time we are doing this to assign variables to our view
$this->view->assign('products', $products);
$this->view->assign('members', $members);
While it would be easier and more readable to just be able to do the following and let the view be responsible to determining the variable name the assigned data gets in our views.
$this->view->assign($products);
$this->view->assign($members);
Short answer: impossible.
Long answer: you could dig through apd, bytekit, runkit, the Reflection API and debug_backtrace to see if any obscure combination would allow you to achieve this behavior.
However, the easiest way is to simply pass the variable name along with the actual variable, like you already do. It's short, it's easy to grasp, it's flexible when you need the variable to have a different name and it is way faster than any possible code that might be able to achieve the other desired behavior.
Keep it simple
removed irrelevant parts after OP edited the question
Regardless of my doubt that this is even possible, I think that forcing a programmer on how to name his variables is generally a bad idea. You will have to answer questions like
Why can't I name my variable $arrProducts instead of $products ?
You would also get into serious trouble if you want to put the return value of a function into the view. Imagine the following code in which (for whatever reason) the category needs to be lowercase:
$this->view->assign(strtolower($category));
This would not work with what you're planning.
My answer therefore: Stick to the 'verbose' way you're working, it is a lot easier to read and maintain.
If you can't live with that, you could still add a magic function to the view:
public function __set($name, $value) {
$this->assign($name, $value);
}
Then you can write
$this->view->product = $product;
I don't think there is any language where this is possible. That's simply not how variables work. There is a difference between a variable and the value it holds. Inside the function foo, you have the value, but the variable that held the value is not available. Instead, you have a new variable $var to hold that value.
Look at it like this: a variable is like a bucket with a name on it. The content (value) of the variable is what's inside the bucket. When you call a function, it comes with its own buckets (parameter names), and you pour the content of your bucket into those (well, the metaphor breaks down here because the value is copied and still available outside). Inside the function, there is no way to know about the bucket that used to hold the content.
What you're asking isn't possible. Even if it was, it would likely be considered bad practice as its the sort of thing that could easily get exploited.
If you're determined to achieve something like this, the closest you can get would be to pass the variable name as a string and reference it in the function from the $GLOBALS array.
eg
function this_aint_a_good_idea_really($var) {
print "Variable name: {$var}\n";
print "Variable contents: {$GLOBALS[$var]}\n";
}
$hello="World";
this_aint_a_good_idea_really('hello');
But as I say, that isn't really a good idea, nor is it very useful. (Frankly, almost any time you resort to using global variables, you're probably doing something wrong)
Its not impossible, you can find where a function was invoked from debug_backtrace() then tokenize a copy of the running script to extract the parameter expressions (what if the calling line is foo("hello $user, " . $indirect($user,5))?),
however whatever reason you have for trying to achieve this - its the wrong reason.
C.
Okay, time for some ugly hacks, but this is what I've got so far, I'll try to work on it a little later
<?php
class foo
{
//Public so we can test it later
public $bar;
function foo()
{
//Init the array
$this->bar = array();
}
function assign($__baz)
{
//Try to figure out the context
$context = debug_backtrace();
//assign the local array with the name and the value
//Alternately you can initialize the variable localy
//using $$__baz = $context[1]['object']->$__baz;
$this->bar[$__baz] = $context[1]['object']->$__baz;
}
}
//We need to have a calling context of a class in order for this to work
class a
{
function a()
{
}
function foobar()
{
$s = "testing";
$w = new foo();
//Reassign local variables to the class
foreach(get_defined_vars() as $name => $val)
{
$this->$name = $val;
}
//Assign the variable
$w->assign('s');
//test it
echo $w->bar['s'];
}
}
//Testrun
$a = new a();
$a->foobar();
impossible - the max. ammount of information you can get is what you see when dumping
debug_backtrace();
Maybe what you want to do is the other way around, a hackish solution like this works fine:
<?php
function assign($val)
{
global $$val;
echo $$val;
}
$hello = "Some value";
assign('hello');
Ouputs: Some value
What you wish to do, PHP does not intend for. There is no conventional way to accomplish this. In fact, only quite extravagant solutions are available. One that remains as close to PHP as I can think of is creating a new class.
You could call it NamedVariable, or something, and as its constructor it takes the variable name and the value. You'd initiate it as $products = new NamedVariable('products', $productData); then use it as $this->view->assign($products);. Of course, your declaration line is now quite long, you're involving yet another - and quite obscure - class into your code base, and now the assign method has to know about NamedVariable to extract both the variable name and value.
As most other members have answered, you are better off suffering through this slight lack of syntactic sugar. Mind you, another approach would be to create a script that recognizes instances of assign()'s and rewrites the source code. This would now involve some extra step before you ran your code, though, and for PHP that's silly. You might even configure your IDE to automatically populate the assign()'s. Whatever you choose, PHP natively intends no solution.
This solution uses the GLOBALS variable. To solve scope issues, the variable is passed by reference, and the value modified to be unique.
function get_var_name(&$var, $scope=FALSE) {
if($scope) $vals = $scope;
else $vals = $GLOBALS;
$old = $var;
$var = $new = 'unique'.rand().'value';
$vname = FALSE;
foreach ($vals as $key => $val) {
if($val === $new) $vname = $key;
}
$var = $old;
return $vname;
}
$testvar = "name";
echo get_var_name($testvar); // "testvar"
function testfunction() {
$var_in_function = "variable value";
return get_var_name($var_in_function, get_defined_vars());
}
echo testfunction(); // "var_in_function"
class testclass {
public $testproperty;
public function __constructor() {
$this->testproperty = "property value";
}
}
$testobj = new testclass();
echo get_var_name($testobj->testproperty, $testobj); // "testproperty"