Automatically use getters and setters with PHP's __get and __set methods? - php

I am trying to build a base class that all of my classes extend from that allows for property access through simple $obj->property syntax, but if get_property() or set_property($value) is defined, then any attempt to get or set that property is routed through those getters and setters.
The tricky part, though, is that I would like updates to object properties to be reflected in an array (which is a property of the object, call it $changed_array) which can output an array of the properties that were changed, for some purpose, say, insertion into a db update call.
The problem lies in this sample:
class Sample {
private $changed_array;
public __get($var_ame){
if(method_exists($this, $method = 'get_' . $var_name)){
return $this->$method();
} else {
return $this->$var_name;
}
}
public __set($var_name, $value){
if(method_exists($this, $method = 'set_' . $var_name))}
return $this->$method($value);
} else {
// pseudo code
if($this->$var_name isset and isn't $value) { // add to $changed_array }
return $this->$var_name = $value;
}
}
}
Which works great, until there is a setter method defined like so:
public set_var_name($value){
// pretend we're mapping a db column to another name
$this->other_var_name = $value;
}
With this, the setter is called, but property it is setting is accessible, so the new value doesn't use the __set or __get function, and the changed array isn't updated with other_var_name as a changed property.
Is there some kind of hack, other that using something like $this->set('variable', 'value') to achieve the result? I would just write getter's and setters, but they vary based on the db schema, and it would be lovely if there was an elegantly simple solution.

Try this,
$objects = array("model", "make", "version");
foreach ($objects as $object) {
$getter = "get".ucfirst($object);
if (is_object($iProduct->$getter())) {
echo "getvalue"+$iProduct->$getter()
}

Related

Property Accessors at Declaration

I'm looking for a way to maintain the $this->property = "value" syntax, but use getter and setter methods.
I found several references to the magic functions __get() and __set(), but I'm looking to provide accessors on more of a case by case basis.
I then found this: https://wiki.php.net/rfc/propertygetsetsyntax-v1.2, which seems like exactly what I was looking for, but alas doesn't seem to have been implemented.
Is there a way to do this without class-wide functions to check every property assignment?
For me, you have two options:
You want to use magic methods to cover all getters and setters, and you'll store your data that would be got/set in a property like $this->_data[] to avoid conflicts with other properties
You want to create specific getters/setters and you manually define them in the classes you want them to be used in (or high up the inheritance chain so they're available to every class that extends it - useful in MVC architecture).
The magic methods approach is a good "cover all bases" approach whereas the individual approach is better for clarity and knowing exactly what is available to implementations/children.
A good example of option 1 (magic methods) is available in the manual, here.
I would add that if you want a "case by case" basis for properties, you could also add a whitelist/blacklist into your magic methods to include/exclude a specific set of properties, example extending what is in the manual:
private $data = array();
private $whitelist = array('PropertyIWant', 'AnotherOneIWant');
// ...
public function __get($name)
{
// ...
if (array_key_exists($name, $this->data) && in_array($name, $this->whitelist)) {
return $this->data[$name];
}
// ...
You could use call:
class Test {
protected $a;
protected $b;
protected $valueForC;
protected $otherData = array('d' => null);
public function __call($method, $args) {
if (preg_match('#^((?:get|set))(.*)$#', $method, $match)) {
$property = lcfirst($match[2]);
$action = $match[1];
if ($action === 'set') {
$this->$property = $args[0];
} else {
return $this->$property;
}
}
}
public function getD() {
return $this->otherData['d'];
}
public function setD($value)
{
$this->otherData['d'] = $value;
}
}
$x = new Test();
$x->setA('a value');
$x->setB('b value');
$x->setValueForC('c value');
$x->setD('special value for D');
echo $x->getA() ."\n";
echo $x->getB() ."\n";
echo $x->getValueForC() ."\n";
echo $x->getD() ."\n";

How to use accessor function

I've read that you can make all your access through a single section of code using accessor function. The book shows me the code, I got it.
But I don't know how to use it. Can someone give me an example or a syntax to use this function please?
The code from my book:
class classname
{
public $attribute;
function __get($name)
{
return $this->$name;
}
function __set($name, $value)
{
$this->$name = $value;
}
}
Accessors provide a way to access private class variables.
An example(let's just say that $attribute is private):
<?php
$classNameObject = new classname();
// Setting the value
$classNameObject->attribute = "A value";
// Getting the value
echo $classNameObject->attribute;
?>
But in php the __set() and __get() functions work in a way that they can create dynamic properties.

PHP Registry Pattern

I've found the piece of code below in several places around the web and even here on Stack Overflow, but I just can't wrap my head around it. I know what it does, but I don't know how it does it even with the examples. Basically it's storing values, but I don't know how I add values to the registry. Can someone please try to explain how this code works, both how I set and retrieve values from it?
class Registry {
private $vars = array();
public function __set($key, $val) {
$this->vars[$key] = $val;
}
public function __get($key) {
return $this->vars[$key];
}
}
It's using PHP's hacked on property overloading to add entries to and retrieve entries from the private $vars array.
To add a property, you would use...
$registry = new Registry;
$registry->foo = "foo";
Internally, this would add a foo key to the $vars array with string value "foo" via the magic __set method.
To retrieve a value...
$foo = $registry->foo;
Internally, this would retrieve the foo entry from the $vars array via the magic __get method.
The __get method should really be checking for non-existent entries and handle such things. The code as-is will trigger an E_NOTICE error for an undefined index.
A better version might be
public function __get($key)
{
if (array_key_exists($key, $this->vars)) {
return $this->vars[$key];
}
// key does not exist, either return a default
return null;
// or throw an exception
throw new OutOfBoundsException($key);
}
You might want to check out PHP.NET - Overloading
Basically, you would do...
$Registry = new Registry();
$Registry->a = 'a'; //Woo I'm using __set
echo $Registry->a; //Wooo! I'm using __get
So here, I'm using __set($a, 'This value is not visible to the scope or nonexistent')
Also, I'm using __get($a);
Hope this helped!

__set and string concatenation

Im' wondering if this is possible:
I've successfully used __set() magic method to set values to properties of a class:
class View
{
private $data;
public function __set( $key, $value )
{
$this->data[$key] = $value;
}
}
So I'm able to:
$view = new View();
$view->whatever = 1234;
The problem comes when I want to concatenate a string for example. It seems like __set() is not being called (it's not being called in fact).
$view = new View();
$view->a_string = 'hello everybody'; //Value is set correctly
$view->a_string.= '<br>Bye bye!'; //Nothing happens...
echo $view->a_string;
This outputs "hello everybody". I'm not able to execute __set() in the second assignment.
Reading php.net it says that:
__set() is run when writing data to inaccessible properties.
So as a_string already exists, __set is not called.
My question finally is... how could I achieve that concatenation operation??
Note:
Ok... Murphy came and gave me the answer as soon as I posted this...
The answer (As I understood), is that PHP is not able to decide if a_string is available as I didn't defined a __get() method.
Defining __get() allows php to find the current value of a_string, then uses __set() to concatenate the value.
You should add a __get() method that allows you to access inaccessable properties just as you did with __set(). You could then do the following:
$view->myvar = $view->myvar.'Added text.';
The __get() method would be:
public function __get($var) {
return (isset($this->data[$var])) ? $this->data[$var]: '';
}
FYI, for simple assignment, magic methods aren't even necessary.
class A {
}
$a = new A();
$a->str = '1';
$a->str .= '2';
echo $a->str; // outputs 12
This will work just fine as PHP will create the new properties through assignment. __set/get usually is used when additional checks/code need to be run on the values.
It's because __set() is a method and not a string. If you want it to "act" like a string you can change it to this.
class View
{
private $data;
public function __set( $key, $value )
{
if (empty($this->data[$key])) {
$this->data[$key] = $value;
} else {
$this->data[$key] .= $value;
}
}
}

json_encode on class with magic properties

I am trying to json_encode an array of objects who all have magic properties using __get and __set. json_encode completely ignores these, resulting in an array of empty objects (all the normal properties are private or protected).
So, imagine this class:
class Foo
{
public function __get($sProperty)
{
if ($sProperty == 'foo')
{
return 'bar!';
}
return null;
}
}
$object = new Foo();
echo $object->foo; // echoes "foo"
echo $object->bar; // warning
echo json_encode($object); // "{}"
I've tried implementing IteratorAggregate and Serializable for the class, but json_encode still doesn't see my magic properties. Since I am trying to encode an array of these objects, an AsJSON()-method on the class won't work either.
Update! It seems the question is easy to misunderstand. How can I tell json_encode which "magic properties" exist? IteratorAggregate didn't work.
BTW: The term from the PHP documentation is "dynamic entities". Whether or not the magic properties actually exist is arguing about semantics.
PHP 5.4 has an interface called JsonSerializable. It has one method, jsonSerialize which will return the data to be encoded. This problem would be easily fixed by implementing it.
json_encode() doesn't "asks" the object for any interface. It directly fetches the HashTable pointer that represents the properties of an object by calling obj->get_properties(). It then iterates (again directly, no interface such as Traversable, Iterator etc. is used) over this HashTable and processes the elements that are marked as public. see static void json_encode_array() in ext/json/json.c
That makes it impossible to have a property to show up in the result of json_encode() but not to be accessible as $obj->propname.
edit: I haven't tested it much and forget about "high performance" but you might want to start with
interface EncoderData {
public function getData();
}
function json_encode_ex_as_array(array $v) {
for($i=0; $i<count($v); $i++) {
if ( !isset($v[$i]) ) {
return false;
}
}
return true;
}
define('JSON_ENCODE_EX_SCALAR', 0);
define('JSON_ENCODE_EX_ARRAY', 1);
define('JSON_ENCODE_EX_OBJECT', 2);
define('JSON_ENCODE_EX_EncoderDataObject', 3);
function json_encode_ex($v) {
if ( is_object($v) ) {
$type = is_a($v, 'EncoderData') ? JSON_ENCODE_EX_EncoderDataObject : JSON_ENCODE_EX_OBJECT;
}
else if ( is_array($v) ) {
$type = json_encode_ex_as_array($v) ? JSON_ENCODE_EX_ARRAY : JSON_ENCODE_EX_OBJECT;
}
else {
$type = JSON_ENCODE_EX_SCALAR;
}
switch($type) {
case JSON_ENCODE_EX_ARRAY: // array [...]
foreach($v as $value) {
$rv[] = json_encode_ex($value);
}
$rv = '[' . join(',', $rv) . ']';
break;
case JSON_ENCODE_EX_OBJECT: // object { .... }
$rv = array();
foreach($v as $key=>$value) {
$rv[] = json_encode((string)$key) . ':' . json_encode_ex($value);
}
$rv = '{' . join(',', $rv) .'}';
break;
case JSON_ENCODE_EX_EncoderDataObject:
$rv = json_encode_ex($v->getData());
break;
default:
$rv = json_encode($v);
}
return $rv;
}
class Foo implements EncoderData {
protected $name;
protected $child;
public function __construct($name, $child) {
$this->name = $name;
$this->child = $child;
}
public function getData() {
return array('foo'=>'bar!', 'name'=>$this->name, 'child'=>$this->child);
}
}
$data = array();
for($i=0; $i<10; $i++) {
$root = null;
foreach( range('a','d') as $name ) {
$root = new Foo($name, $root);
}
$data[] = 'iteration '.$i;
$data[] = $root;
$root = new StdClass;
$root->i = $i;
$data[] = $root;
}
$json = json_encode_ex($data);
echo $json, "\n\n\n";
$data = json_decode($json);
var_dump($data);
There is at least one flaw: It doesn't handle recursion, e.g.
$obj = new StdClass;
$obj->x = new StdClass;
$obj->x->y = $obj;
echo json_encode($obj); // warning: recursion detected...
echo json_encode_ex($obj); // this one runs until it hits the memory limit
From your comment:
I am asking about the magic properties, not the methods. – Vegard Larsen 1 min ago
There is no such thing as a magic property.
All you have right now is a magic method that gets called when you try to access a non-visible property.
Let's say it again
$obj->foo does not exist
The way a magic method is implemented is not a concern of PHP, and it can't magically know that you are using your magic method to 'magically' make it look like there is $obj->foo.
If a property does not exist, it will not be put into the object when you json_encode it.
Furthermore, even if json_encode knew that __get was active, it wouldn't know what value to use to call it.
This is an incredibly old thread, but to not confuse other posters who are interested in how magic methods are used to get/set properties in PHP and how that affects JSON encoding, here is my understanding.
Including __get and __set methods in a class give you an interface to deal with dynamically named properties for a given class. Most people define an internal associative array that does the housekeeping.
The reason your foo/bar example doesn't show up in JSON encoding is because you are simply creating an association that returns a value. It's not setting a property in the object itself to that value.
If you did something like:
public function set($name, $value) {
$this->data[$name] = $value;
}
Then if you called :
$foo = new ObjectClass;
$foo->set('foo','bar');
now there is a element in that array $data['foo'] = 'bar'
if you json_encode the object, then that relationship will be represented.
json_encode does not encode methods, only properties. There's no way around it. For your code, the functional equivalent of your magic __get, would be to include in the constructor a call to set, that "hardwires" the value of the property named "foo".
Ultimately, I don't know what specifics you are wrestling with as you didn't provide much in the way of detail. However, json_encoding a object or an array will simply give you a list of properties that are available. If you have to pass through an interpreting function and are relying on that somehow, that's a different problem that I unfortunately have no answer for.
I hope this helps even though it is part of a downrated thread, which ironically, IMO, contains the solution.
I'd create a method on the object to return the internal array.
class Foo
{
private $prop = array();
public function __get($sProperty)
{
return $this->prop[$sProperty];
}
public function __set($sProperty, $value)
{
$this->prop[$sProperty] = $value;
}
public function getJson(){
return json_encode($this->prop);
}
}
$f = new Foo();
$f->foo = 'bar';
$json = $f->getJson();
This is the correct behavior
JSON is only able to contain data not methods - it is meant to be language independent so encoding object methods would not make sense.
How could it know your properties?
$object = new Foo();
echo $object->foo; // how do you know to access foo and not oof?
// how do you expect json_encode to know to access foo?
magic methods are syntactic sugar, and mostly fire back when you use them. this is one such case.
echo json_encode($object); // "{}"
of course it's empty, $object has zero public properties, just a magic method and the ensuing syntactic sugar (turned sour)
i have found the following that worked for me creating classes with dynamic properties and outputing them with json_encode. Maybe it helps other people to.
http://krisjordan.com/dynamic-properties-in-php-with-stdclass

Categories