Dynamically add private properties to an object - php

When you have the following code:
<?php
class Foo { public $attribute; }
$o = new Foo();
$o->bar = true;
?>
PHP automatically creates a dynamic public property to that object.
Is there any possibility to do add dynamic private properties? Or set them to private at runtime with Reflection?
My __set method needs to be called every time someone tries to set any attribute in my class. I could set all my attributes to private, but, I have attributes that are added dynamically in this class, and when an attribute is added automatically, it has a public visibility.
This prevents the __set method from being called. How can I make dynamic properties call __set when receiving a value?
Actually, there's a method on ReflectionProperty class named setAccessible(). I would do the opposite of this method.

This doesn't seem possible in current versions of PHP.
While ReflectionProperty::setAccessible() does take a boolean argument, the change it makes only allows Reflection itself to access / not access the value. It doesn't actually change the accessibility of the actual property.
As a hacky workaround to keep dynamic properties private, consider having your __set store properties that don't actually exist in a dedicated private array. Example code:
class Test {
private $foo;
public $bar;
private $_properties;
public function __get($prop) {
if(property_exists($this, $prop))
return $this->$prop;
if(array_key_exists($prop, $this->_properties))
return $this->_properties[$prop];
}
public function __set($prop, $value) {
if(!property_exists($this, $prop)) {
$this->_properties[$prop] = $value;
echo 'SetDynamic: ', $prop, "\n";
return;
}
$this->$prop = $value;
echo 'Set: ', $prop, "\n";
}
}
Running from the PHP interactive prompt:
php > $t = new Test;
php > $t->foo = 1;
Set: foo
php > $t->foo = 2;
Set: foo
php > $t->bar = 1;
php > $t->testorama = 1;
SetDynamic: testorama
php > $t->testorama = 2;
SetDynamic: testorama
While this will ensure that external access always goes through your __get and __set methods, it presents a problem for internal use, as you're now always given two places to check for dynamic properties. That's why this is a hackish workaround instead of a real solution.

Related

Dynamically changing attribute in PHP

I have some abstract knowledge of OOP but this is the first time I am trying to code some OOP in PHP. I want to create a class that will have some attributes from construction but some attributes that dynamically change.
I am a little confused about all the terms (objects, classes, methods,...) so I do not know exactly what to search for. I made a simplified example below.
This is where I declared my class, that will accept 2 parameters on construction and calculate the third one, which is the higher number (please ignore that I don't check the type).
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
$this->p_max = max(array($this->p1, $this->p2));
}
}
Then I initialize the object and check the p_max:
$test = new test(1,2);
echo $test->p_max; // Prints 2
But if I change p1 and p2, the p_max won't change:
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max; // Prints 2 (I want 4)
How should I define the p_max inside my class to update every time I change p1 or p2? Is there a way without turning p_max into a method?
You can achieve this, using the magic __get method, which will be called, if a property of a class is accessed, but not defined. This is pretty hacky in my opinion, but works just as you want it to.
<?php
class test {
public function __construct($p1, $p2) {
$this->p1 = $p1;
$this->p2 = $p2;
}
public function __get($name) {
if ('p_max' === $name) {
return max(array($this->p1, $this->p2));
}
}
}
$test = new test(1,2);
echo $test->p_max; // Prints 2
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max; // Prints 4
Doing it this way, the max value will be calculated every time, you access this property.
Edit: Because the __get method will only be called for a property, which is not defined in the class itself, this wont work, if you assign the variable a value in the constructor or create it as property.
Edit2: I´d like to point out - again - that it´s pretty hacky to do it this way. For a way cleaner way, go with AbraCadaver´s answer. That´s how I personally would do it, too.
You don't really need to use a magic method, just a method that returns the calculated value:
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
}
public function p_max() {
return max($this->p1, $this->p2);
}
}
$test->p1 = 3;
$test->p2 = 4;
echo $test->p_max(); // call method
You can also accept optional arguments to p_max() to set new values and return the calculated value:
class test{
public function __construct($p1, $p2){
$this->p1 = $p1;
$this->p2 = $p2;
}
public function p_max($p1=null, $p2=null) {
$this->p1 = $p1 ?? $this->p1;
$this->p2 = $p2 ?? $this->p2;
return max($this->p1, $this->p2);
}
}
echo $test->p_max(3, 4); // call method
Also notice that max accepts multiple arguments so you don't have to specify an array.

Why can't I run class code outside of a method?

I heard static method are used when object has not been instantiated or when you need to call something within a class.
class Show_files{
static private $person = 1;
echo Show_files::$person++;
}
I'd like this to show 2, I know it will work if I put it in an instance or in a method, but why's it not working like it is now?
You are trying to execute code inside a class, but outside of a method. That is illegal syntax. The only things that can go "outside" of the methods are attribute definitions:
class foo {
static $x = 0; // simple variable creation, fixed values. A-OK
public $y = 1+1; // illegal - expressions not permitted
private $z = self::$y++; // double-illegal
echo self::$z // illegal - executing code
}
The $z definition is illegal on two levels - you cannot create $z by reading from the object while it's still being parsed/defined, and you cannot "execute code" (i.e. an "expression") in an attribute definition.
Becouse it's inconsistent with php syntax - you have to place it inside of a method.
Ex.
Show_files{
static private $person = 1;
public static function show()
{
echo self::person++;
}
}
and then Show_files::show()
Change it to
class Show_files{
static private $person = 1;
public static function getPersonCount() {
self::$person++;
echo self::$person;
return;
}
}
and call it
Show_files::getPersonCount();
You have to put this echo inside a method. And then call it elsewhere (for exemple, your view). Watch this : http://php.net/manual/en/language.oop5.basic.php

How to programmatically find public properties of a class from inside one of it's methods

I've got a class Foo with public and protected properties. Foo needs to have a non-static method, getPublicVars() that returns a list of all the public properties of Foo (this is just an example, I know from outside the Foo object calling get_object_vars() will accomplish this and there is no need for my getPublicVars() method).
Note: This must also return dynamically declared properties assigned at runtime to the class instance (object) that aren't defined in the class's definition.
Here's the example:
class Foo{
private $bar = '123';
protect $boo = '456';
public $beer = 'yum';
//will return an array or comma seperated list
public function getPublicVars(){
// thar' be magic here...
}
}
$foo = new Foo();
$foo->tricky = 'dynamically added var';
$result = $foo->getPublicVars();
var_dump($result); // array or comma list with 'tricky' and 'beer'
What is the most concise way to get the only the public properties of an object from inside a class's own methods where both public and protected are visible?
I've looked at:
What is the best way to look inside a PHP class instance (object) to see all of its available public properties and methods?
But this doesn't seem to address my question as it points to using get_object_vars() from outside the object.
As you already realized, PHP's build in get_object_vars is scope-sensitive. You want the public object properties only.
So from that function to the public variant is not a large step:
function get_object_public_vars($object) {
return get_object_vars($object);
}
Calling this get_object_public_vars will give you only the public properties then because it is place out of scope of the current object.
If you need more fine-grained control, you can also make use of the ReflectionObject:
(new ReflectionObject($this))->getProperties(ReflectionProperty::IS_PUBLIC);
Which has the benefit that you don't need to introduce another function in the global namespace.
Does not work with php version >=7
As such, I can't really recommend solution any longer.
Use reflection instead
To get the public properties from within the class
$publicProperties = call_user_func('get_object_vars', $this);
the "trick" is that get_object_vars is being called from the scope of call_user_func and not the scope of the object
no need for reflection, stand-alone functions, closures, etc
Another (PHP 7.* compatible) way is to cast $this to an array and filter out any binary keys. This is a little less verbose than using Reflection.
return array_filter(
(array) $this,
static fn(string $key): bool => strpos($key, "\0") !== 0, ARRAY_FILTER_USE_KEY,
);
Using the ReflectionClass, you can do this easily. For example, below, I make an associative array of all the public properties.
$rc = new \ReflectionClass($this);
//get all the public properties.
$props = $rc->getProperties(\ReflectionProperty::IS_PUBLIC);
$ret = [];
foreach($props as $p) {
//get the name and value of each of the public properties.
$ret[$p->getName()] = $p->getValue($this);
}
You can wrap it into a closure and bind it to nowhere, then you'll have an outer scope view to the local public properties.
public function getPublicVars(){
return \Closure::bind(fn($obj) => get_object_vars($obj),null,null)($this);
}
According to this article (written by Vance Lucas), you can create a new call scope inside your "Foo" class definition using an "anonymous" function, and then you can call get_object_vars() from within. This allow you to get only the public properties from inside your class, even if these have been created dynamically later from the outside.
So adapted to your example it would be:
<?php
class Foo {
private $bar = '123';
protected $boo = '456';
public $beer = 'yum';
// return an array of public properties
public function getPublicVars(){
$publicVars = create_function('$obj', 'return get_object_vars($obj);');
return $publicVars($this);
}
}
$foo = new Foo();
$foo->tricky = 'dynamically added var';
$result = $foo->getPublicVars();
print_r($result);
and the output will be:
Array
(
[beer] => yum
[tricky] => dynamically added var
)
There is a second example in the article mentioned above that shows another way to do the same using the so-called "closures" (from php 5.3) but for some reason it doesn't work for me with php v5.4 so the private and protected properties remains included in the resulting array.

Extending json_encode for nested objects

I am using PHP 5.2.x and want to encode objects of my custom PHP classes with only private members. One of the private members is an array of objects of another custom class.
I tried the solution outlined in https://stackoverflow.com/a/7005915/17716, but that obviously does not work recursively. The only solution that I can see is extending the json_encode method somehow ,so that the class' version of the json_encode method is called instead of the default method.
For reference, the code is as follows:
Class A {
private $x;
private $y;
private $z;
private $xy;
private $yz;
private $zx;
public function f1() {
...
}
public function f2() {
...
}
.
.
.
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
}
class B {
private $p; //This finally stores objects of class A
private $q;
private $r;
public function __construct() {
$this->p = array();
}
public function fn1() {
...
}
public function fn2() {
...
}
.
.
.
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
}
class C {
private $arr;
public function __construct() {
$this->arr = array();
}
public function fillData($data) {
$obj = new B();
//create objects of class B and fill in values for all variables
array_push($this->arr, $obj)
}
public function returnData() {
echo $this->arr[0]->getJSONEncode(); //Edited to add
}
}
What would be the best way to achieve this nested json encoding?
Edited to Add:
The output I get when the returnData method is executed is:
{"p":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"q":"Lorem Ipsum","r":"Dolor Sit Amet"}
Whilst I believe you would be better off writing a proper export/encode function for each of your classes (which would construct a public object from both private and public values just for encoding - you could be quite clever and use php's reflection ability) - instead - you could make use of the following code:
/// example class B
class TestB {
private $value = 123;
}
/// example class A
class TestA {
private $prop;
public function __construct(){
$this->prop = new TestB();
}
}
function json_encode_private($obj){
/// export the variable to find the privates
$exp = var_export($obj, true);
/// get rid of the __set_state that only works 5.1+
$exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
/// rebuild the object
eval('$enc = json_encode('.$exp.');');
/// return the encoded value
return $enc;
}
echo json_encode_private(new TestA());
/// {"prop":{"value":123}}
So the above should work, but I wouldn't recommend using eval anywhere in php - just because I always hear alarm bells quietly off in the distance :)
update
Just had a thought of what might make this a little safer, rather than using eval you could use create_function which would limit some of its creational powers, or at least the scope of those powers...
function json_encode_private($obj){
$exp = var_export($obj, true);
$exp = preg_replace('/[a-z0-9_]+\:\:__set_state\(/i','((object)', $exp);
$enc = create_function('','return json_encode('.$exp.');');
return $enc();
}
update 2
Had a chance to play around with another way of converting an object with private properties, to an object with public properties - using only a simple function (and no eval). The following would need to be tested on whichever version of PHP you are using as it's behaviour - again - might not be reliable.... due to the weird \0Class Name\0 prefixing in the converted private properties (see comments in code).
For more info on this strange prefixing behaviour:
http://uk3.php.net/language.types.array.php#language.types.array.casting
Anyway, so using a test class:
class RandomClass {
private $var = 123;
private $obj;
public function __construct(){
$this->obj = (object) null;
$this->obj->time = time();
}
}
We can use the following function to convert it to a public object:
function private_to_public( $a ){
/// grab our class, convert our object to array, build our return obj
$c = get_class( $a ); $b = (array) $a; $d = (object) null;
/// step each property in the array and move over to the object
/// usually this would be as simple as casting to an object, however
/// my version of php (5.3) seems to do strange things to private
/// properties when casting to an array... hence the code below:
foreach( $b as $k => $v ){
/// for some reason private methods are prefixed with a \0 character
/// and then the classname, followed by \0 before the actual key value.
/// This must be some kind of internal protection causing private
/// properties to be ignored. \0 is used by some languges to terminate
/// strings (not php though, as far as i'm aware).
if ( ord($k{0}) === 0 ) {
/// trim off the prefixed weirdnesss..?!
$e = substr($k, 1 + strlen($c) + 1);
/// unset the $k var first because it will remember the \0 even
/// if other values are assigned to it later on....?!
unset($k); $k = $e;
}
/// so if we have a key, either public or private - set our value on
/// the destination object.
if ( $k !== '' && $k !== NULL && $k !== FALSE ) {
$d->{$k} = $v;
}
}
return $d;
}
So if we put it all together:
$a = new RandomClass();
echo json_encode( private_to_public( $a ) );
/// {"var":123,"obj":{"time":1349777323}}
Again your best / most reliable bet is to either bespokely code your conversion methods for each class, or create some kind of generalised solution using Class Reflection, but that latter is far more involved, more involved than a StackOverflow answer... at least with the amount of time I have free ;)
further info
The above code will work when trying to access objects from anywhere, the reason for implementing this was because my first attempt was obviously to use the following:
echo json_encode( get_object_vars($a) );
/// you will get {} which isn't what you expect
It seems that if you want to use get_object_vars you have to use it from a context that has access to all the properties, i.e. from inside the class you are exposing:
public function getAllProperties(){
return get_object_vars( $this );
}
So, imagine we'd added the above to the RandomClass definition:
echo json_encode( $a->getAllProperties() );
/// you will get {"var":123,"obj":{"time":1349777323}}
This works because the members of a class have access to all the class's properties public or private.... so as I say, working this way is far far superior; superior, but not always possible.

How do I create a PHP static class property at runtime (dynamically)?

I'd like to do something like this:
public static function createDynamic(){
$mydynamicvar = 'module';
self::$mydynamicvar = $value;
}
and be able to access the property from within the class with
$value = self::$module;
I don't know exactly why you would want to do this, but this works. You have to access the dynamic 'variables' like a function because there is no __getStatic() magic method in PHP yet.
class myclass{
static $myvariablearray = array();
public static function createDynamic($variable, $value){
self::$myvariablearray[$variable] = $value;
}
public static function __callstatic($name, $arguments){
return self::$myvariablearray[$name];
}
}
myclass::createDynamic('module', 'test');
echo myclass::module();
static variables must be part of the class definition, so you can't create them dynamically. Not even with Reflection:
chuck at manchuck dot com 2 years ago
It is important to note that calling ReflectionClass::setStaticPropertyValue will not allow you to add new static properties to a class.
But this looks very much like a XY Problem. You probably don't really want to add static properties to a PHP class at runtime; you have some use case that could be fulfilled also that way. Or that way would be the fastest way, were it available, to fulfill some use case. There well might be other ways.
Actually the use cases below are yet again possible solutions to some higher level problem. It might be worth it to reexamine the high level problem and refactor/rethink it in different terms, maybe skipping the need of meddling with static properties altogether.
I want a dictionary of properties inside my class.
trait HasDictionary {
private static $keyValueDictionary = [ ];
public static function propget($name) {
if (!array_key_exists($name, static::$keyValueDictionary) {
return null;
}
return static::$keyValueDictionary[$name];
}
public static function propset($name, $value) {
if (array_key_exists($name, static::$keyValueDictionary) {
$prev = static::$keyValueDictionary[$name];
} else {
$prev = null;
}
static::$keyValueDictionary[$name] = $value;
return $prev;
}
}
class MyClass
{
use Traits\HasDictionary;
...$a = self::propget('something');
self::propset('something', 'some value');
}
I want to associate some values to a class, or: I want a dictionary of properties inside some one else's class.
This actually happened to me and I found this question while investigating ways of doing it. I needed to see, in point B of my workflow, in which point ("A") a given class had been defined, and by what other part of code. In the end I stored that information into an array fed by my autoloader, and ended up being able to also store the debug_backtrace() at the moment of class first loading.
// Solution: store values somewhere else that you control.
class ClassPropertySingletonMap {
use Traits\HasDictionary; // same as before
public static function setClassProp($className, $prop, $value) {
return self::propset("{$className}::{$prop}", $value);
}
public static function getClassProp($className, $prop) {
return self::propget("{$className}::{$prop}");
}
}
// Instead of
// $a = SomeClass::$someName;
// SomeClass::$someName = $b;
// we'll use
// $a = ClassPropertySingletonMap::getClassProp('SomeClass','someName');
// ClassPropertySingletonMap::setClassProp('SomeClass','someName', $b);
I want to change, not create, an existing property of a class.
// Use Reflection. The property is assumed private, for were it public
// you could do it as Class::$property = $whatever;
function setPrivateStaticProperty($class, $property, $value) {
$reflector = new \ReflectionClass($class);
$reflector->getProperty($property)->setAccessible(true);
$reflector->setStaticPropertyValue($property, $value);
$reflector->getProperty($property)->setAccessible(false);
}
Static properties must be defined in the class definition. Therefore, real static properties cannot be created dynamically like regular properties.
For example, if you run this:
<?php
class MyClass
{
public static function createDynamic()
{
$mydynamicvar = 'module';
self::$mydynamicvar = $value;
}
}
MyClass::createDynamic();
var_dump(MyClass::$mydynamicvar);
var_dump(MyClass::$module);
...you'll get this error
Fatal error: Access to undeclared static property: MyClass::$mydynamicvar test.php on line 8
Notice how the error occurs on line 8 when trying to set the property instead of line 14 or 15 (as you might expect if you were simply doing it wrong and dynamically creating static properties was actually possible).
A related problem that IS possible (in PHP 5.4.0 and up) is to include various separate groups of static variable or constant declarations and group them together into one class declaration.
Here is an example:
trait Added1 // This can be located in one Include file
{
static
$x="hello"; // Can declare more variables here
}
trait Added2 // This can be located in another Include file
{
static
$y="world"; // Can declare more variables here
}
class G // Global constant and variable declarations class
{
use Added1, Added2; // Combines all variable declarations
}
echo G::$x." ".G::$y; // Shows "hello world" on the web page

Categories