<?php
/**
* Strategy for converting a dynamic property that is initialized only when the property is invoked.
*
* This strategy declares the property but sets its visibility to private. By setting it to
* private (opr protected), the original intent still works, meaning it's only init and set when
* code invokes it (which invokes the __get()).
*/
class DynamicPropInitOnGet {
public $prop1;
public $prop2;
public $prop3;
private $known;
public function __construct( $props ) {
foreach ( $props as $prop => $value ) {
$this->$prop = $value;
}
}
public function __get( $prop ) {
if ( 'known' !== $prop ) {
trigger_error(
sprintf( 'Getting the dynamic property "%s" on %s is deprecated', $prop, __CLASS__ ),
E_USER_DEPRECATED
);
return null;
}
$this->known = new stdClass();
foreach ( array( 'prop1', 'prop2' ) as $column ) {
$this->known->{$column} = isset( $this->{$column} ) ? $this->{$column} : null;
}
return $this->known;
}
public function __isset( $prop ) {
if ( 'known' === $prop ) {
return isset( $this->known );
}
return false;
}
public function __set( $prop, $value ) {
if ( 'known' === $prop ) {
$this->known = $value;
return;
}
trigger_error(
sprintf( 'Setting the dynamic property "%s" on %s is deprecated', $prop, __CLASS__ ),
E_USER_DEPRECATED
);
}
public function __unset( $prop ) {
if ( 'known' === $prop ) {
unset( $this->known );
return;
}
trigger_error(
sprintf( 'Unsetting the dynamic property "%s" on %s is deprecated', $prop, __CLASS__ ),
E_USER_DEPRECATED
);
}
}
class TestMagicMethods {
private $obj;
private $is_known_prop = false;
public function __construct( $obj ) {
$this->obj = $obj;
}
public function test( $prop ) {
$this->is_known_prop = ( 'known' === $prop );
// __isset.
$this->test_isset( $prop );
// __get().
$this->test_get( $prop );
// __isset() after __get().
$this->test_isset( $prop, true );
// __unset().
$this->test_unset( $prop );
}
private function test_isset( $prop, $after_get = false ) {
$actual = isset( $this->obj->$prop );
if ( ! $after_get ) {
printf( "__isset() results should be false %s\n", $this->test_results( false === $actual ) );
} else {
$expected = $this->is_known_prop;
printf(
"\n\n__isset() after __get() results should be %s %s\n",
$this->is_known_prop ? 'true' : 'false',
$this->test_results( $expected == $actual )
);
}
var_dump( $actual );
}
private function test_get( $prop ) {
$actual = $this->obj->$prop;
$message = "\n\n__get() results should be %s %s\n";
if ( $this->is_known_prop ) {
printf( $message, 'an object with prop1 and prop2', $this->test_results( $actual instanceof stdClass ) );
} else {
printf( $message, 'null', $this->test_results( null === $actual ) );
}
var_dump( $actual );
}
private function test_set( $prop ) {
$value = $this->is_known_prop ? (array) $this->$obj->known : 'I am an unknown dynamic property';
$obj->$prop = $value;
$actual = $this->obj->$prop;
if ( $this->is_known_prop ) {
printf(
"\n\n" .
'__set() results should set the value %s and not throw a deprecation on PHP 8.2+.' .
'However, once the __get() is called again, it will set reset it to an object with prop1 and prop2.' .
"\n",
$this->test_results( $actual instanceof stdClass )
);
} else {
printf(
"\n\n" .
'__set() results should set the value %s and not throw a deprecation on PHP 8.2+.' .
'The set value should remain set when invoking __get()' .
"\n",
$this->test_results( $value === $actual )
);
}
var_dump( $actual );
}
private function test_unset( $prop ) {// unset.
unset( $this->obj->$prop );
$actual = $this->obj->$prop;
$message = "\n\n__unset() + __get() results should be %s %s\n";
if ( $this->is_known_prop ) {
printf(
$message,
're-init the object with prop1 and prop2',
$this->test_results( $actual instanceof stdClass )
);
} else {
printf(
$message,
'null',
$this->test_results( null === $actual )
);
}
var_dump( $actual );
}
private function test_results( $actual ) {
return $actual ? '✅' : '❌';
}
}
$obj = new DynamicPropInitOnGet(
array(
'prop1' => 'foo',
'prop2' => 'bar',
'prop3' => 'baz',
)
);
$tester = new TestMagicMethods( $obj );
echo "***** Test the 'known' property *****\n\n";
$tester->test( 'known' );
echo "\n\n***** Unknown, unexpected dynamic property *****\n\n";
$tester->test( 'unknown' );
preferences:
37.76 ms | 407 KiB | 5 Q