views:

49

answers:

4

I was writing a class that uses __get() and __set() to store and retrieve array elements in a master array. I had a check to make some elements ungettable, basically to re-create private properties.

I noticed that it seemed that __get intercepts all calls to class properties. This sucks for me, because I wanted to have a variable private to the outside world ( unavailable via get ), but I was trying to access it by directly referencing the master array from within the class. Of course, the master array is not in the whitelist of gettable properties :(

Is there a way I can emulate public and private properties in a php class that uses __get() and __set()?

Example:

<?

abstract class abstraction {

    private $arrSettables;
    private $arrGettables;
    private $arrPropertyValues;
    private $arrProperties;

    private $blnExists = FALSE;

    public function __construct( $arrPropertyValues, $arrSettables, $arrGettables ) {

        $this->arrProperties = array_keys($arrPropertyValues);
        $this->arrPropertyValues = $arrPropertyValues;
        $this->arrSettables = $arrSettables;
        $this->arrGettables = $arrGettables;
    }

    public function __get( $var ) {
        echo "__get()ing:\n";
        if ( ! in_array($var, $this->arrGettables) ) {
            throw new Exception("$var is not accessible.");
        }

        return $this->arrPropertyValues[$var];
    }

    public function __set( $val, $var ) {
        echo "__set()ing:\n";
        if ( ! in_array($this->arrSettables, $var) ) {
            throw new Exception("$var is not settable.");
        }

        return $this->arrPropertyValues[$var];
    }

} // end class declaration

class concrete extends abstraction {

    public function __construct( $arrPropertyValues, $arrSettables, $arrGettables ) {
        parent::__construct( $arrPropertyValues, $arrSettables, $arrGettables );
    }

    public function runTest() {

        echo "Accessing array directly:\n";
        $this->arrPropertyValues['color'] = "red";
        echo "Color is {$this->arrPropertyValues['color']}.\n";

        echo "Referencing property:\n";
        echo "Color is {$this->color}.\n";
        $this->color = "blue";
        echo "Color is {$this->color}.\n";

        $rand = "a" . mt_rand(0,10000000);
        $this->$rand = "Here is a random value";
        echo "'$rand' is {$this->$rand}.\n";

    }
}

try {
    $objBlock = & new concrete( array("color"=>"green"), array("color"),  array("color") );
    $objBlock->runTest();
} catch ( exception $e ) {
    echo "Caught Exeption $e./n/n";
}

// no terminating delimiter

$ php test.php
Accessing array directly:
__get()ing:
Caught Exeption exception 'Exception' with message 'arrPropertyValues is not accessible.' in /var/www/test.php:23
Stack trace:
#0 /var/www/test.php(50): abstraction->__get('arrPropertyValu...')
#1 /var/www//test.php(68): concrete->runTest()
#2 {main}.
+1  A: 

Is there a way I can emulate public and private properties in a php class that uses __get() and __set()?

Not directly (if you discount debug_backtrace).

But you can have a private method getPriv that does all the work your current __get does. Then __get would only wrap this private method and check accessibility.

function __get($name) {
    if (in_array($name), $this->privateProperties)
        throw new Exception("The property ". __CLASS__ . "::$name is private".);
    return $this->getPriv($name);
}

Inside your class, you would call getPriv, thus bypassing __get.

Artefacto
But there's still a problem -- since I'm storing everything in an array, `getPriv()` has to reference the value in the class property `$arrPropertyValues`, which is intercepted by `__get()`... infinite loop?
I'll be darned! It works! But why? Is it because `__get()` doesn't intercept in the originating class?
@user It does intercept in the originating class – hence inside the class you should call `getPriv`. I'm not sure I understood you, but `__get` does not intercept method calls or property access if the properties are explicitly declared.
Artefacto
Oh now I see. I was confused -- I thought `getPriv()`'s reference to `$arrPropertyvalues[$var]` would be intercepted by `__get()`, but since it `$arrPropertyValues` is declared, it's not intercepted. Right?
BTW I meant to upvote you but I mucked it up when I undid the upvote when I thought there would be an infinite loop.
@user Well, you can now; I've edited the question :p And yes, that's it.
Artefacto
BTW, what's the `debug_backtrace` method?
A: 

You should use underscore (_) as prefix of your private attributes.

Felipe Cardoso Martins
What would this accomplish exactly?
Artefacto
http://pear.php.net/manual/en/standards.php
Felipe Cardoso Martins
@Felipe I mean, what does it have to do with the question. And by the way, just just a coding standard PEAR uses, it's very arguable whether it makes sense in general.
Artefacto
There's a proposal to drop the _ prefix requirement from PEAR's coding standard, and this proposal has near-unanimous support. http://pear.php.net/pepr/pepr-proposal-show.php?id=99
Bill Karwin
+1 It's and adopted convention, pretty much what Python does, and was a widespread arrangement in PHP4 too. So no need to diss the proposal.
mario
If I've made them private in the abstract class, how should I prefix them?
+1  A: 

Make abstraction::$arrPropertyValues protected or do what Artefacto wrote (if you need additional checks), except that abstraction::getPriv() should be protected.

morkai
Protected does the trick!
A: 

Rather than manually enlisting private/protected properties, you could use PHPs cumbersome reflection methods:

function __get($name) {

    $reflect = new ReflectionObject($this);
    $publics = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);

    if (in_array($name, $publics)) {
         return $this->{$name};
    }
}
mario
Yummmm... cumbersome! :)
cucumbersome to be precise
mario
Mmmmm.. cummerbunds....