Author Topic: dynamic getter/setter  (Read 2566 times)

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
dynamic getter/setter
« on: March 06, 2011, 11:00:57 PM »
I have a class that is using a lot of variables and I started to wonder if there was an easy way to create setters and getters since my editor doesn't do it for me.  So then I started wondering if there was a way to call them dynamically as long as you know the name of the variable.  And as it turns out, there is a way in PHP:

Code: [Select]
function __call($method, $arguments) {
        $prefix = strtolower(substr($method, 0, 3));
        $property = strtolower(substr($method, 3));

        if (empty($prefix) || empty($property)) {
            return;
        }

        if ($prefix == "get" && isset($this->$property)) {
            return $this->$property;
        }

        if ($prefix == "set") {
            $this->$property = $arguments[0];
        }
    }

I'm using it and it seems to be working but I'm curious if anyone can think of drawbacks to this method?  It saves me a ton of time and it's easy to call for anything in the class.

And now I'm wondering if this same capability is available in Java... surely it is?

Mike

  • Jackass In Charge
  • Posts: 11257
  • Karma: +168/-32
  • Ex Asshole - a better and more caring person.
Re: dynamic getter/setter
« Reply #1 on: March 06, 2011, 11:40:29 PM »
The magic methods are slower (I'd have to do some benchmarking to give accurate numbers).

For getter/setters I would use the magic methods __get() and __set() (see: http://php.net/manual/en/language.oop5.magic.php)  From your code snippet I take it you are using it like:

$obj
->setfoo(5);
$foo $obj->getfoo();


You could so something like:

class Foo
{
  private 
$data = array();
  public function 
__set($name$value)
  {
    
$this->data[$name] = $value;
  }

  public function 
__get($name)
  {
    return isset(
$this->data[$name]) ? $this->data[$name] : null;
  }
}


However, IIRC __get and __set don't work on actual properties.  So if you tried to do

$obj
->data['blah'] = 9;


I think you'll get an error since $data is private.


I actually used __call to handle dynamic error handling.  I wanted to be able to either return false or throw an exception and control that at run time.  So I prefixed the actual methods and make them protected (so you couldn't call them directly).  The calls use the method names without the prefix.  Example:

class Foo
{
  protected function 
throwable_bar()
  {
    echo 
'You suck';
  }
  public function 
__call($func$args)
  {
    
$func "throwable_" $func;
    
call_user_func_array($args);
  }


I'd have bar do normal exception error handling and __call would catch it (or not).  IIRC I had to use reflection to build a list of functions that can be called that way.

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
Re: dynamic getter/setter
« Reply #2 on: March 07, 2011, 08:49:10 AM »
Yeah, I saw an example using an array of data for the dynamic get/set but I didn't think that was as elegant and I fucking hate arrays for some unknown reason.

I'm curious about the performance hit I would take using the method above.

Perspective

  • badfish
  • Jackass In Charge
  • Posts: 4635
  • Karma: +64/-22
    • http://jeff.bagu.org
Re: dynamic getter/setter
« Reply #3 on: March 07, 2011, 09:06:02 AM »

I'm using it and it seems to be working but I'm curious if anyone can think of drawbacks to this method?  It saves me a ton of time and it's easy to call for anything in the class.

And now I'm wondering if this same capability is available in Java... surely it is?


Java has something similar, it's called relfection. (works for method invocation, not sure if you can use it to access variables though). http://java.sun.com/developer/technicalArticles/ALT/Reflection/

There is a performance overhead in using reflection, but the bigger problem is that simple errors become run-time errors instead of compile-time errors (e.g., spelling a method name wrong).

Mike

  • Jackass In Charge
  • Posts: 11257
  • Karma: +168/-32
  • Ex Asshole - a better and more caring person.
Re: dynamic getter/setter
« Reply #4 on: March 07, 2011, 10:07:10 AM »
Yeah, I saw an example using an array of data for the dynamic get/set but I didn't think that was as elegant and I fucking hate arrays for some unknown reason.

I'm curious about the performance hit I would take using the method above.

Seriously?  You hate arrays?  Man, in PHP I think 50% or more of my variables are arrays.  It is also the only language that I've used more than 2d arrays on a regular basis.

I have a feeling I'll be a bit bored at work today (current project is in the "oh fuck how am I gonna do this?" stage) so I'll run some benchmarks on your method and the __get, __set method.

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
Re: dynamic getter/setter
« Reply #5 on: March 07, 2011, 10:22:53 AM »
Thanks Mike.

webwhy

  • Jackass IV
  • Posts: 608
  • Karma: +15/-10
Re: dynamic getter/setter
« Reply #6 on: March 07, 2011, 12:16:17 PM »
If you're interested in how Ruby and/or Python handle this situation, let me know.  i don't want to derail the thread.  This is one of the pains in PHP development that ultimately led me to Ruby since I feel it has the most elegant solution compared to Java and PHP...Python takes a slightly different approach, but it's a similar enough idea.

Mike

  • Jackass In Charge
  • Posts: 11257
  • Karma: +168/-32
  • Ex Asshole - a better and more caring person.
Re: dynamic getter/setter
« Reply #7 on: March 07, 2011, 02:29:13 PM »
Wasn't sure exactly how you were doing the calls so let me know if you have a better way.

So I tried 4 different methods:
1) Direct set/get using public properties.  Basically the baseline.
2) Using the __set, __get magic methods that has the same interface as the Direct method
3) Using "standard" set() and get() functions we were all taught.
4) Ober's method.

For each method it got and set three variables over 100,000 iterations.

Results:
MethodDirectMagicStandardOber
Time (s)0.0795588493347170.843012094497680.550071001052862.9130039215088
Times longer110.59608203923356.914014036812236.6144551595174

File:
Code: [Select]
<?php

class Direct
{
public $foo;
public $bar;
public $baz;
}

class 
Standard
{
private $foo;
private $bar;
private $baz;

public function set($var$value)
{
$this->$var $value;
}

public function get($var)
{
return property_exists($this$var) ? $this->$var null;
}
}

class 
Ober
{
private $foo;
private $bar;
private $baz;

function __call($method$arguments)
{
$prefix strtolower(substr($method03));
$property strtolower(substr($method3));

if (empty($prefix) || empty($property))
{
return;
}

if ($prefix == "get" && property_exists($this$property))
{
return $this->$property;
}

if ($prefix == "set")
{
$this->$property $arguments[0];
}
}
}

class 
Magic
{
private $data = array(
'foo' => null,
'bar' => null,
'baz' => null,
);

function __set($var$val)
{
$this->data[$var] = $val;
}

function __get($var)
{
return array_key_exists($var$this->data) ? $this->data[$var] : null;
}
}

$objs = array(
'direct' => new Direct,
'magic' => new Magic,
);
$ober = new Ober;
$standard = new Standard;

$times = array();

$iterations 100000;

$vars = array('foo''bar''baz');

// Direct and magic have the same type of interface
foreach($objs AS $name => $obj)
{
// Initalize the values
foreach($vars AS $var)
$obj->$var 0;

$start microtime(true);
for ($i 0$i $iterations$i++)
{
foreach($vars AS $var)
$obj->$var++;
}
$end microtime(true);

$times[$name] = $end $start;
}

// Standard get/set functions
$obj $standard;
foreach(
$vars AS $var)
$obj->set($var0);

$start microtime(true);
for (
$i 0$i $iterations$i++)
{
foreach($vars AS $var)
$obj->set($var$obj->get($var)+1);
}
$end microtime(true);

$times['standard'] = $end $start;

// Ober's function
$obj $ober;
foreach(
$vars AS $var)
{
$func "set$var";
$obj->$func(0);
}

$start microtime(true);
for (
$i 0$i $iterations$i++)
{
foreach($vars AS $var)
{
$set "set$var";
$get "get$var";
$obj->$set($obj->$get()+1);
}
}
$end microtime(true);

$times['ober'] = $end $start;


echo 
'<pre>'print_r($objs); print_r($ober); print_r($standard); print_r($times); die('</pre>');

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
Re: dynamic getter/setter
« Reply #8 on: March 07, 2011, 02:39:21 PM »
Shit... I guess it is way worse.

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
Re: dynamic getter/setter
« Reply #9 on: March 07, 2011, 10:25:17 PM »
However, IIRC __get and __set don't work on actual properties.  So if you tried to do

$obj
->data['blah'] = 9;


I think you'll get an error since $data is private.
I don't know that I'd care since I'd have a dynamic set function.  Now it's a toss up.  Do I rewrite my class to work with this?  I'm only actually using the dynamic thing in one instance but it is called repeatedly.

ober

  • Ashton Shagger
  • Ass Wipe
  • Posts: 14310
  • Karma: +73/-790
  • mini-ober is taking over
    • Windy Hill Web Solutions
Re: dynamic getter/setter
« Reply #10 on: March 07, 2011, 10:32:14 PM »
I think I'm going to retain all my getters and setters and just build the get/set string when I need to call them dynamically (I didn't actually know you could do that last night) because I'm already using a lot of gets/sets throughout my code that call them directly.