使用__get或类似方法访问Array类型的类成员变量

Previously a class which I'm now rebuilding had a member variable $settings which was an array of settings, strangely enough.

class MyClass {
    public $settings = array();

    public function __construct() {
        if( empty( $this->settings ) ) {
            $this->settings = require( 'settings.php' ); // e.g. return array('setting1'=>4);
        }
    }
}

These settings were accessed by $object->settings['keyname'];

The means by which these keys are accessed has been moved into a method now. However, the application itself is riddled with calls to $object->settings['keyname']. I was wondering is there a way which I can catch any calls to the $settings member variable and return it using the new function.

I've looked at __get($name) but $name only contains settings rather than the array key which I need. What I'd need to pass would be the keyname to the my $object->get() method.

The reason I want to do this is so that I can trigger errors in a log file showing me where the deprecated calls to $object->settings[] are without breaking the application. Obviously setting $setting to private would give me lots of fatal errors and I could work through but there are multiple developers working on this codebase which I'd prefer not to break. If I could implement this as a temporary solution it'd help.

I realise there are repositories etc which we could use so that I could work on it separately and check it in afterwards but I'm looking for a quick, temporary solution as we're porting our codebase to Git soonish.

Totally possible:

<?php
class Logger {
    public function log( $name, $backtrace ) {
        /**
         * $name is the name of the array index that was called on
         * MyClass->settings( ). $backtrace contains an array in which
         * you can find and determine which file has accessed that index,
         * and on which line.
         */
        $last = array_shift( $backtrace );
        $message = sprintf( "Setting '%s' was called by file '%s' on line %d",
            $name,
            $last['file'],
            $last['line']
        );

        echo $message;  

    }
}

class MyClass {
    protected $settings;

    public function __construct( ) {
        $this->settings = new Settings( new Logger( ), array( 'foo' => 'bar' ) );
    }

    public function __get( $name ) {
        if( $name === 'settings' ) {
            return $this->settings;
        }
    }
}

class Settings extends ArrayObject {
    protected $logger;
    protected $settings;
    public function __construct( Logger $logger, array $settings ) {
        $this->logger = $logger;
        parent::__construct( $settings );
    }

    public function offsetGet( $name ) {
        $backtrace = debug_backtrace( );
        $this->logger->log( $name, $backtrace );
        return parent::offsetGet( $name );
    }
}

$myClass = new MyClass( );
echo $myClass->settings['foo'] . "
";

Sure, it's hackish, and you probably don't want to keep this in your codebase, but for logging deprecated uses, it might be extremely helpful. Just log for a set period of time, and then replace the $this->settings = new Settings( ) with $this->settings = array( ).

By the way, the output of that exact code is the following:

berry@berry-pc:~/Desktop% php foo.php
Setting 'foo' was called by file '/home/berry/Desktop/foo.php' on line 53
bar

To find where scripts SET a variable in settings:

I would suggest putting a tiny dirty hack in the constructor and the destructor of your MyClass class. In the constructor scan the $settings and check if a deprecated value exists (save it somewhere temp). In the destructor do the same check, and cross match the two results. When you have new variables during the destruction you know that the $_SERVER['PHP_SELF'] has set it.

To find where scripts access it:

Brutal, but I would recommend just converting all deprecated values to new ones, and watch things stop working. You'll spend less time tracking down what broke, and reading the "This used to work and doesn't" complaints, than you will modifying your class to use a logger