I just found something "kinda strange" about PHP 7.4 and I am not sure if it is just me missing something or maybe if it is an actual bug. Mostly I am interested in your opinion/confirmation.
So in PHP, you can iterate over objects properties like this:
class DragonBallClass
{
public $goku;
public $bulma = 'Bulma';
public $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
RESULT
NULL
string(5) "Bulma"
string(6) "Vegeta"
Now if we start using strongly typed properties like that:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
foreach ($dragonBall as $character) {
var_dump($character);
}
We will get a different result:
string(5) "Bulma"
string(6) "Vegeta"
Now what is different:
When you DO NOT assign a default value to strongly typed property it will be of Uninitialized type. Which of course makes sense. The problem is though that if they end up like this you cannot loop over them they will simply be omitted - no error, no anything as you can see in the second example. So I just lose access to them.
It makes sense but just imagine that you have a custom Request/Data class like this:
namespace App\Request\Request\Post;
use App\Request\Request\Request;
class GetPostsRequest extends Request
{
public string $title = '';
}
Do you see that ugly string assignment? If I want to make my properties on the class iterable then I have to either:
drop types
assign dummy values
I might want to have an object with typed properties without any values in them to loop over them and populate them if that makes sense.
Is there any better way of doing this? Is there any option to keep types and keep em iterable without having to do this dummy value abomination?
If you want to allow a typed attribute to be nullable you can simply add a ? before the type and give NULL as default value like that:
class DragonBallClass
{
public ?string $goku = NULL;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
In this case NULL is a perfectly legitimate value (and not a dummy value).
demo
Also without using ?, you can always merge the class properties with the object properties lists:
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$dragonBall = new DragonBallClass();
$classProperties = get_class_vars(get_class($dragonBall));
$objectProperties = get_object_vars($dragonBall);
var_dump(array_merge($classProperties, $objectProperties));
// array(3) {
// ["goku"]=>
// NULL
// ["bulma"]=>
// string(5) "Bulma"
// ["vegeta"]=>
// string(6) "Vegeta"
// }
Before we start - I think that the answer accepted by me and provided by Casimir is better and more correct than what I came up with(that also goes for the comments).
I just wanted to share my thoughts and since this is a working solution to some degree at least we can call it an answer.
This is what I came up with for my specific needs and just for fun. I was curious about what I can do to make it more the way I want it to be so don't freak out about it ;P I think that this is a quite clean workaround - I know it's not perfect though.
class MyRequest
{
public function __construct()
{
$reflectedProperties = (new \ReflectionClass($this))->getProperties();
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
}
}
class PostRequest extends MyRequest
{
public ?string $title;
}
$postRequest = new PostRequest();
// works fine - I have access here!
foreach($postRequest as $property) {
var_dump($property);
}
The downfall of this solution is that you always have to make types nullable in your class. However for me and my specific needs that is totally ok. I don't mind, they would end up as nullables anyway and it might be a nice workaround for a short deadline situation if someone is in a hurry.
It still keeps the original PHP not initialized error though when the type is not nullable. I think that is actually kinda cool now. You get to keep all the things: Slim and lean classes, PHP error indicating the true nature of the problem and possibility to loop over typed properties if you agree to keep them nullable. All governed by native PHP 7 nullable operator.
Of course, this can be changed or extended to be more type-specific if that makes any sense.
Update: this answer may be obsolete, but the comments contain an interesting discussion.
#Robert's workaround is buggy; in this part:
foreach ($reflectedProperties as $property) {
!$property->isInitialized($this) ??
$property->getType()->allowsNull() ? $property->setValue($this, null) : null;
}
the ?? must be corrected to &&.
Moreover that's a misuse of the ternary conditional; just use a classic if:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this)
&& $property->getType()->allowsNull()
) {
$property->setValue($this, null);
}
}
or:
foreach ($reflectedProperties as $property) {
if (!$property->isInitialized($this) && $property->getType()->allowsNull()) {
$property->setValue($this, null);
}
}
Probably not what you want, but you could use reflection:
<?php
class DragonBallClass
{
public string $goku;
public string $bulma = 'Bulma';
public string $vegeta = 'Vegeta';
}
$ob = new DragonBallClass;
$reflector = new ReflectionClass($ob);
foreach($reflector->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
echo $prop->name, ':', ($ob->{$prop->name} ?? 'NOT INITIALISED'), "\n";
}
Output:
goku:NOT INITIALISED
bulma:Bulma
vegeta:Vegeta
Related
How can I get PHP to evaluate a static variable in double quotes?
I want to do something like this:
log("self::$CLASS $METHOD entering");
I've tried all sorts of {} combos to get the variable value of self::$CLASS, but nothing has worked. I've currently settled with string concatenation but it is a pain to type:
log(self::$CLASS . " $METHOD entering");
Sorry, you can't do that. It only works for simple expressions. See here.
Unfortunately there is no way how to do this yet. Example in one of answers here will not work, because {${self::$CLASS}} will not returns content of self::$CLASS, but will returns content of variable with name in self::$CLASS.
Here is an example, which does not returns myvar, but aaa:
$myvar = 'aaa';
self::$CLASS = 'myvar';
echo "{${self::$CLASS}}";
Use an anonymous identity function stored in a variable. This way you will have $ immediately after {:
$I = function($v) { return $v; };
$interpolated = "Doing {$I(self::FOO)} with {$I(self::BAR)}";
(I am using class constants in this example but this will work with static variables too).
I don’t know the answer to your question, but you can show the class name and method using the __METHOD__ magic constant.
<?php
class test {
public $static = 'text';
public $self = __CLASS__;
// static Method
static function author() {
return "Frank Glück";
}
// static variable
static $url = 'https://www.dozent.net';
public function dothis() {
$self = __CLASS__;
echo <<<TEST
{${!${''}=static::author()}} // works
{$self::author()} // works
{$this->self::author()} // works
${!${''}=self::author()} // works
{${$this->self}}::author()}} // don't works
${${self::author()}} // do/don't works but with notice
${#${self::author()}} // works but with # !
TEST;
}
}
$test = 'test'; // this is the trick, put the Classname into a variable
echo "{$test::author()} {$$test::$url}";
echo <<<HTML
<div>{$test::author()}</div>
<div>{$$test::$url}</div>
HTML;
$test = new test();
$test->dothis();
I know this is an old question but I find it odd that noone has suggested the [sprintf][1] function yet.
say:
<?php
class Foo {
public static $a = 'apple';
}
you would use it with:
echo sprintf( '$a value is %s', Foo::$a );
so on your example its:
log(
sprintf ( ' %s $METHOD entering', self::$CLASS )
);
//define below
function EXPR($v) { return $v; }
$E = EXPR;
//now you can use it in string
echo "hello - three is equal to $E(1+2)";
Just live with the concatenation. You'd be surprised how inefficient variable interpolation in strings can be.
And while this could fall under the umbrella of pre-optimization or micro-optimization, I just don't think you actually gain any elegance in this example.
Personally, if I'm gonna make a tiny optimization of one or the other, and my choices are "faster" and "easier to type" - I'm gonna choose "faster". Because you only type it a few times, but it's probably going to execute thousands of times.
Yes this can be done:
log("{${self::$CLASS}} $METHOD entering");
Consider this scenario where I want to SOMETIMES pass data through to my view:
public function show($orderId)
{
if ($sometimesTrue == true)
{
$optionalParameter = 'optional parameter';
}
return view('show', compact([$a, $b, $c, $optionalParameter]));
}
$optionalParameter is not always set, so I want to know what my options are for setting individual, optional view parameters without re-arranging the structure of the function.
In Zend, the following is possible:
$this->view->optionalParameter = $optionalParameter;
Which can go anywhere in the controller method, not just at the end where the view is instantiated. Of course, back in Laravel, I could do something this:
public function show($orderId)
{
$paramaterArray = [$a, $b, $c];
if ($sometimesTrue == true)
{
$optionalParameter = 'optional parameter';
$paramaterArray[] = $optionalParameter;
}
return view('show', compact($paramaterArray));
}
But re-arranging entire functions because a optional parameter is introduced seems a bit limiting. Is there any way I can set an individual parameter for a view?
You can just built your own protected function + protected property in the Controller class. You could do something like this:
Beneath is using the splat operator so it will only work in php 5.6 >=
protected $optionalParameter;
protected function optionalcompact(...$parameters) {
if(!empty($this->optionalParameter)){
return compact($parameters, $this->optionalParameter);
} else {
return compact($parameters);
}
}
Then back in your own built controller class you can do this:
public function show($orderId)
{
if ($sometimesTrue == true)
{
$this->optionalParameter = 'optional parameter';
}
return view('show', $this->optionalcompact($a, $b, $c));
}
I would simply use the following
public function show($orderId)
{
$paramaterArray = [$a, $b, $c];
$paramaterArray['optional'] = $sometimesTrue == true ? 'optional parameter' : '';
return view('show', $paramaterArray);
}
Since this is an optional parameter so I don't need to check every time in my view whether it's set or not, simply {{ $optional }} will work better, it'll be printed if any value is set or nothing will be printed if the $optional variable is empty. This way, I'll remain consistant.
Also, you may check the Larave's View Composers, it may help you.
Researching further, it seems that using compact with the names of the variables (as opposed to the variables themselves) will silently ignore the missing variable which gives the intended behaviour:
$var1 = 'bob';
$var2 = 'nigel';
var_dump(compact('var1', 'var2', 'var3'));
Returns:
array(2) { ["var1"]=> string(3) "bob" ["var2"]=> string(5) "nigel" }
Which works perfectly for my scenario. Using it this way, the controller method doesn't require refactoring and no additional coding is required. Whether relying on compact to not issue a warning is good coding practice is another question.
Just to confirm, calling compact with the variables themselves WILL throw a notice warning:
$var1 = 'bob';
$var2 = 'nigel';
var_dump(compact($var1, $var2, $var3));
Returns:
NOTICE Undefined variable: var3 on line number 4
This Class gives me a blank output even if I change return to echo, I'm not sure what the issue is but I'm obviously not that versed in dealing with Classes and Objects.
I'm sure I'm just handling the variables/arrays incorrectly, but I can't see where, maybe the variables shouldn't be declared under Class since they should only be returned if a person is created? Should I declare variables in the function, or not declare them at all since they should be handled by $args?
Updated Question: How do I get it to return every argument not just FIRSTNAME?
PHP:
class people_handler
{
public $firstname;
public $middlename;
public $lastname;
public $city;
public $province_state;
/* zip+4 is default for postcode (postal code) */
public $postcode;
public $country;
function create_people($args)
{
$fullname=array($this->firstname,$this->middlename,$this->lastname);
$normname=array($this->firstname,$this->lastname);
$fulladdress=array($this->city,$this->province_state,$this->postcode,$this->country);
if(!$args->middlename&&$args->firstname && $args->lastname && $args->city && $args->province_state && $args->postcode && $args->country)
{
$temp_arr=array($normname,$fulladdress);
foreach($temp_arr as $value)
{
foreach($value as $values)
{
return $values;
}
}
}
else if($args->firstname && $args->middlename && $args->lastname && $args->city && $args->province_state && $args->postcode && $args->country)
{
$temp_arr=array($fullname,$fulladdress);
foreach($temp_arr as $value)
{
foreach($value as $values)
{
return $values;
}
}
}
else
{
die ("Must enter all values excluding middlename.");
}
}
}
$p1=new people_handler;
$p1->firstname="John";
$p1->middlename="Jonah";
$p1->lastname="Jameson";
$p1->city="Lansing";
$p1->province_state="Michigan";
$p1->postcode="48876-4444";
$p1->country="USA";
echo $p1->create_people($p1);
Returns:
John
You're missing the Object self-reference: $this all over the place.
Anytime you refer to a method or property from within the class, you need to refer to $this as the current instantiation of the Object that is doing the process. So, for instance...
$fullname=array($firstname,$middlename,$lastname);
becomes
$fullname=array($this->firstname,$this->middlename,$this->lastname);
Which should work, since you assigned the values to those properties already.
EDIT: Looking at the code further, constantly returning a value through loops won't manage the echoing to the browser. You can either echo $value instead of returning it, or build an array from the values and return that and have the script handle the array to echo to the browser.
EDIT THE SECOND: To get all the values out, you need to collect them as you build them. Another option is to simply output them to the browser as part of the method. Both options work, but collecting them into an array makes it more portable, but also a fair bit more code to maintain. As well, you do not need to pass the object into itself to get the method to work.
echo $p1->create_people($p1);
Should be...
$p1->create_people();
In create_people you'll have...
function create_people()
{
$fullname=array($this->firstname,$this->middlename,$this->lastname);
$normname=array($this->firstname,$this->lastname);
$fulladdress=array($this->city, $this->province_state, $this->postcode, $this->country);
if($args->firstname && $args->lastname && $args->city && $args->province_state && $args->postcode && $args->country)
{ //Don't bother including middlename if it doesn't matter if it is filled or not...
$temp_arr = array($normname, $fulladdress);
foreach($temp_arr as $value)
{
foreach($value as $values)
{
echo $values;
}
}
} else {
die ("Must enter all values excluding middlename.");
}
}
That should work.
Apart from the self-reference problem (btw the $args is also not needed as this should be the self-reference), your loop structure is wrong.
$temp_arr=array($normname,$fulladdress);
foreach($temp_arr as $value)
{
foreach($value as $values)
{
return $values;
}
}
This will:
Loop through temp_arr, finding $normname as the first value
Treat $normname as an array and loop through it
Return the first value it finds in $normname
That concludes the function, everything else is not executed.
A function can only have one return value. If you need to return information on more than one thing, you need to return it as an array or as an object so that it is all wrapped up in one element.
At the moment I'm not quite sure what you're trying to accomplish with your class, so unfortunately I can't help you with what you need to do.
Edit: You don't need to return anything in that case. Your class makes those variables accessible to all functions within the class already. With "new" you create an instance of the object, that is you create "a people_handler". This people_handler has properties about it, which you made public, so they can be set from outside the class (which may not be a great idea in a bigger project but seems fine for this). All functions which are part of the class (that is, inside it), can access what values these properties currently have for that certain people_handler by using the self-reference, $this:
class TestClass {
public fullname; //a random "property"
function echoFullname() {
echo $this->fullname; //whatever fullname is at the moment for the TestClass object we are using
}
}
$a = new TestClass(); //Create a TestClass object
$a->fullname = "Alex"; //make its name "Alex"
$b = new TestClass(); //Create another TestClass object
$b->fullname = "Carl"; //but let's name him Carl
$a->echoFullname(); //And now output the names
$b->echoFullname();
Obviously this has no practical use but hopefully illustrates how it works.As you can see, variable passing wasn't necessary at all.
at line 14:
$fullname=array($firstname,$middlename,$lastname);
Probably should be:
$fullname=array($this->firstname,$this->middlename,$this->lastname);
same one line 16:
$fulladdress=array($city,$province_state,$postcode,$country);
I have lots of code like this in my constructors:-
function __construct($params) {
$this->property = isset($params['property']) ? $params['property'] : default_val;
}
Some default values are taken from other properties, which was why I was doing this in the constructor. But I guess it could be done in a setter instead.
What are the pros and cons of this method and is there a better one?
Edit: I have some dependencies where if a property is not supplied in the $params array then the value is taken from another property, however that other property may be optional and have a default value, so the order in which properties are initialized matters.
This means that if I used getters and setters then it is not obvious which order to call them in because the dependencies are abstracted away in the getter instead of being in the constructer...
I would suggest you, to write proper getter/setter functions, which assert you the correct data-type and validations (and contain your mentioned default-value logic). Those should be used inside your constructor.
When setting multiple fields, which depend on each other, it seems to be nice to have a separate setter for this complex data. In which kind of way are they depending anyway?
e.g.:
// META-Config
protected $static_default_values = array(
"price" => 0.0,
"title" => "foobar"
// and so on
);
protected $fallback_getter = array(
"price" => "getfallback_price"
);
// Class Logic
public function __construct($params){
$this->set_properties($params);
}
public set_properties($properties){
// determines the sequence of the setter-calls
$high_prio_fields = array("price", "title", "unimportant_field");
foreach($high_prio_fields as $field){
$this->generic_set($field, $properties[$field]);
// important: unset fields in properties-param to avoid multiple calls
unset($properties[$field]);
}
foreach($properties as $field => $value){
$this->generic_set($field, $value);
}
}
// this could also be defined within the magic-setter,
// but be aware, that magic-functions can't be resolved by your IDE completely
// for code-completion!
private function generic_set($field, $value){
// check if setter exists for given field-key
$setter_func = "set_".$v;
if(method_exists($this, $setter_func){
call_user_func_array(array($this, $setter_func), array($v));
}
// else => just discard :)
}
// same comment as generic-set
private function generic_get($field){
// check if value is present in properties array
if(isset($this->properties[$field]){
return $this->properties[$field];
}
// check if fallback_getter is present
if(isset($this->fallback_getter[$field]){
return call_user_func_array(array($this, $this->fallback_getter[$field]));
}
// check for default-value in meta-config
if(isset($this->static_default_values[$field]){
return $this->static_default_values[$field];
}
// else => fail (throw exception or return NULL)
return null;
}
public function get_price(){
// custom getter, which ovverrides generic get (if you want to)
// custom code...
return $this->generic_get("price");
}
private function getfallback_price(){
return $this->properties["other_value"] * $this->properties["and_another_value"];
}
public function set_price($price){
$price = (float) $price; // convert to correct data-type
if($price >= 0.0){
$this->properties["price"] = $price;
}
// else discard setting-func, because given parameter seems to be invalid
// optional: throw exception or return FALSE on fail (so you can handle this on your own later)
}
Update to your edit:
the modified source-code should solve all your demands (order of setter-funcs, different resolvings of get-value).
Create "globally available" function array_get.
public static function array_get($array, $property, $default_value = null) {
return isset($array[$property]) ? $array[$property] : $default_value;
}
When having a lot of default options and you need to be able to overwrite them - as you have maybe seen in jQuery using .extend() before - I like to use this simple and quick method:
class Foo {
private $options;
public function __construct($override = array()) {
$defaults = array(
'param1' => 'foo',
'param2' => ...,
'paramN' => 'someOtherDefaultValue');
$this->options= array_replace_recursive($defaults, $override);
}
}
Especially for getting classes started this is a very easy and flexible way, but as already has been mentioned if that code is going to be heavily used then it probably not a bad idea to introduce some more control over those options with getters and setters, especially if you need to take actions when some of those options are get or set, like in your case dependencies if I understood your problem correctly.
Also note that you don't have to implement getters and setters yourself, in PHP you can use the __get and __set magic methods.
It follows some useless code that hopefully gives some ideas:
[...inside Foo...]
public function __set($key, $value){
switch(true){
//option exists in this class
case isset($this->options[$key]):
//below check if $value is callable
//and use those functions as "setter" handlers
//they could resolve dependencies for example
$this->options[$key] = is_callable($value) ? $value($key) : $value;
break;
//Adds a virtual setter to Foo. This so called 'magic' __set method is also called if the property doesn't exist in the class, so you can add arbitrary things.
case $key === 'someVirtualSetterProp': Xyzzy::Noop($value); break;
default:
try{ parent::__set($key, $value); } catch(Exception $e){ /* Oops, fix it! */ }
}
}
Note that in the above examples I squeezed in different approaches and it usually doesn't make sense to mix them like that. I did this only to illustrate some ideas and hopefully you will be able to decide better what suits your needs.
So, I have a object with structure similar to below, all of which are returned to me as stdClass objects
$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;
etc... (note these examples are not linked together).
Is it possible to use variable variables to call contact->phone as a direct property of $person?
For example:
$property = 'contact->phone';
echo $person->$property;
This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this.
Any ideas?
In response to answers relating to proxy methods:
And I would except this object is from a library and am using it to populate a new object with an array map as follows:
array(
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
);
and then foreaching through the map to populate the new object. I guess I could envole the mapper instead...
If i was you I would create a simple method ->property(); that returns $this->contact->phone
Is it possible to use variable variables to call contact->phone as a direct property of $person?
It's not possible to use expressions as variable variable names.
But you can always cheat:
class xyz {
function __get($name) {
if (strpos($name, "->")) {
foreach (explode("->", $name) as $name) {
$var = isset($var) ? $var->$name : $this->$name;
}
return $var;
}
else return $this->$name;
}
}
try this code
$property = $contact->phone;
echo $person->$property;
I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first.
For example:
$property = 'contact->phone';
echo $person->{$property};
The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly.
$xml->{a-disallowed-field}
If it is legal it does not mean it is also moral. And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. Take a look at the law of demeter:
Law of Demeter
try this if you really really want to:
json_decode(json_encode($person),true);
you will be able to parse it as an array not an object but it does your job for the getting not for the setting.
EDIT:
class Adapter {
public static function adapt($data,$type) {
$vars = get_class_vars($type);
if(class_exists($type)) {
$adaptedData = new $type();
} else {
print_R($data);
throw new Exception("Class ".$type." does not exist for data ".$data);
}
$vars = array_keys($vars);
foreach($vars as $v) {
if($v) {
if(is_object($data->$v)) {
// I store the $type inside the object
$adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
} else {
$adaptedData->$v = $data->$v;
}
}
}
return $adaptedData;
}
}
OOP is much about shielding the object's internals from the outside world. What you try to do here is provide a way to publicize the innards of the phone through the person interface. That's not nice.
If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures:
class conv {
static function phone( $person ) {
return $person->contact->phone;
}
}
// imagine getting a Person from db
$person = getpersonfromDB();
print conv::phone( $p );
If ever you need a more specialized function, you add it to the utilities. This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability.
Another way is to 'extend' the Person class with conveniences, built around the core class' innards:
class ConvPerson extends Person {
function __construct( $person ) {
Person::__construct( $person->contact, $person->name, ... );
}
function phone() { return $this->contact->phone; }
}
// imagine getting a Person from db
$person = getpersonfromDB();
$p=new ConvPerson( $person );
print $p->phone();
You could use type casting to change the object to an array.
$person = (array) $person;
echo $person['contact']['phone'];
In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures.
In the example above, person has contact and dob. The contact also contains address. Trying to access the data from the uppermost level is not uncommon when writing complex database applications. However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects.
As much as I hate saying it, you could do an eval :
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. Do remember that magic methods are somewhat slow though.
class Person {
private $fields = array();
//...
public function __get($name) {
if (empty($this->fields)) {
$this->fields = get_class_vars(__CLASS__);
}
//Cycle through properties and see if one of them contains requested field:
foreach ($this->fields as $propName => $default) {
if (is_object($this->$propName) && isset($this->$propName->$name)) {
return $this->$propName->$name;
}
}
return NULL;
//Or any other error handling
}
}
I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. Thank for you for your answers.
Edit:
If you are interested:
public function __construct($data)
{
$this->_raw = $data;
}
public function getContactPhone()
{
return $this->contact->phone;
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
}
if (isset($this->_raw->$name)) {
return $this->_raw->$name;
}
return null;
}
In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. You can then 'decorate' your objects with the same retrieval code.
An example of 'retrieval only' decoration code:
function retrieve( $obj, $path ) {
$element=$obj;
foreach( $path as $step ) {
$element=$element[$step];
}
return $element;
}
function decorate( $decos, &$object ) {
foreach( $decos as $name=>$path ) {
$object[$name]=retrieve($object,$path);
}
}
$o=array(
"id"=>array("name"=>"Ben","surname"=>"Taylor"),
"contact"=>array( "phone"=>"0101010" )
);
$decorations=array(
"phone"=>array("contact","phone"),
"name"=>array("id","name")
);
// this is where the action is
decorate( $decorations, &$o);
print $o->name;
print $o->phone;
(find it on codepad)
If you know the two function's names, could you do this? (not tested)
$a = [
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
];
foreach ($a as $name => $chain) {
$std = new stdClass();
list($f1, $f2) = explode('->', $chain);
echo $std->{$f1}()->{$f2}(); // This works
}
If it's not always two functions, you could hack it more to make it work. Point is, you can call chained functions using variable variables, as long as you use the bracket format.
Simplest and cleanest way I know of.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Usage
echo getValueByPath($person,'contact->email');
// Returns the value of that object path