开发者

Property chaining and isset in configuration object [duplicate]

This question already has answers here: 开发者_StackOverflow社区 Search nested object for property (4 answers) Closed 2 years ago.

I couldn't find a question that was quite like mine, but if you can find one feel free to let me know..

I'm trying to figure out how to effectively create an neat configuration object.

I want the object (or a config manager of some sort) to be able to convert an array or INI file into a parent/child grouping of objects.

Eg:

$config_array = array ( 
    'somecategory' => array ( 'key' => 'somevalue' ), 
    'anothercategory' => array ( 'key' => 'anothervalue', 'key2' => 'anothervalue2' ),
);

$config = new Configuration_Array( $config_array );

echo $config->somecategory->key; // prints: 'somevalue'

I have effectively done this with the following code:

class Configuration_Array extends Configuration
{

protected $_child_class = 'Configuration_Array';
protected $_objects = null;
protected $_config = null;


public function __construct( $config_array = null, $child_class = null ) {
    if ( null !== $config_array ) {
        $this->setConfig( $config_array );
    }
    if ( null !== $child_class ) {
        $this->setChildClass( $child_class );
    }
}

public function __get( $name ) {
    $name = strtolower( $name );
    if ( ! isset ( $this->_objects[ $name ] ) ) {
        $this->_createObject( $name );
    }
    return $this->_objects[ $name ];
}

public function __isset( $name ) {
    $name = strtolower( $name );
    return ( ( isset ( $this->_objects[ $name ] ) ) or $this->_can_create_object( $name ) );        
}


public function reset() {
    $this->_objects = null;
}

public function toArray() {
    if ( ! is_array ( $this->_config ) ) {
        throw new Exception( 'No configuration has been set' );
    }
    return $this->_config;
}

public function setConfig( $config ) {
    if ( null === $config ) {
        return $this->reset();
    }
    if ( ! is_array ( $config ) ) { 
        throw new Exception( 'Configuration is not a valid array' );
    }
    $this->_config = $config;
}

public function loadConfig( $path ) {
    if ( ! is_string ( $path ) ) {
        throw new Exception( 'Configuration Path "' . $path . '" is not a valid string' );
    }
    if ( ! is_readable ( $path ) ) {
        throw new Exception( 'Configuration file "' . $path . '" is not readable' );
    }
    $this->setConfig( include( $path ) );
}

public function setChildClass( $class_name ) {
    if ( ! is_string ( $class_name ) ) {
        throw new Exception( 'Configuration Child Class is not a valid string' );
    }
    if ( ! class_exists ( $class_name ) ) {
        throw new Exception( 'Configuration Child Class does not exist' );
    }
    $this->_child_class = $class_name;
}

public function getChildClass() {
    if ( ! isset ( $this->_child_class ) ) {
        throw new Exception( 'Configuration Child Class has not been set' );
    }
    return $this->_child_class;
}


protected function _createObject( $name ) {
    $name = strtolower( $name );
    if ( ! isset ( $this->_config[ $name ] ) ) {
        throw new Exception( 'No configuration has been set for object "' . $name . '"' );
    }
    $child_class = $this->getChildClass();
    if ( is_array ( $this->_config[ $name ] ) ) {
        $child = new $child_class( $this->_config[ $name ], $child_class );
    } else {
        $child = $this->_config[ $name ];
    }       
    return ( $this->_objects[ $name ] = $child );
}


protected function _can_create_object( $name ) {
    $name = strtolower( $name );
    return isset ( $this->_config[ $name ] );       
}

}

The Problem

Most of this works perfectly, but I am having some trouble figuring out how I can use isset effectively. With property chaining, isset only works on the last value in the chain, eg:

if ( isset ( $config->somecategory->key ) ) { 

Which uses the object returned by $config->somecategory and checks whether it holds an object called 'key'

This means that if $config->somecategory doesn't exist, an exception is thrown. The user would have to do this to check effectively:

if ( isset ( $config->somecategory ) and isset ( $config->somecategory->key ) ) { 

But that seems quite annoying.

An array on the other hand doesn't need to be checked at each level; PHP can check the entire thing:

if ( isset ( $config[ 'somecategory' ][ 'key' ] ) ) { // No error/exception

What I'm looking for is a way to implement my class so I can treat my objects sort of the same way I'd treat an array:

if ( isset ( $config->somecategory->key ) ) {

In a way that wouldn't throw an exception if 'somecategory' doesn't exist...

Ideas?


Since PHP 7 it's possible to use a not well documented feature of null coalesce operator for this purpose.

$config_array = [ 
    'somecategory' => [ 'key' => 'somevalue' ], 
    'anothercategory' => [ 'key' => 'anothervalue', 'key2' => 'anothervalue2' ],
];

// Quickly convert to object
$json = json_encode($config_array);
$config = json_decode($json);

echo $config->somecategory->key ?? null; // prints: 'somevalue'
echo $config->somecategory->missing_key ?? null; // no errors but also doesn't print anything
echo $config->somecategory->missing_key->go->crazy->with->chains ?? null; // no errors but also doesn't print anything

Here is an online example in action


Unfortunately there is not version of isset which checks your property chain correctly. Even writing your own method will not help as passing the chain as parameter to your method already fails if somecategory is already unset.


You can implement the magic method for accessing unset properties (maybe in a base class common to your config objects). This will create a dummy object of class UnsetProperty and return that.

Your class to $config->someCategory->key will deliver a UnsetProperty for $config->someCategory. This object will also delivery a new UnsetProperty for $obj->key. If you implement a method IsSet() in UnsetProperty returning false and in other properties returning true you can simplyfy your check to:

if($config->someCategory->key->IsSet()) ...

This will need a lot of to do so I am not sure if you do not like to go with the chained isset-calls.

if((isset($config->someCategory)) and (isset($config->someCategory->key))) ...

Depends on style and how many nested levels you have.

Hope you get the idea behind the possibility.


Take a look at Zend_Config. It operates almost exactly as you describe.

You could use it directly or simply as an instructional guide to build your own.


Maybe something like this?

The only problem is, you would have to call isEmpty to check if a configuration is given, and get to get the final value. (Like can be seen in the 3 test cases at the bottom)

<?php
// set debug
error_reporting(E_ALL ^ E_STRICT);
ini_set('display_errors', 'on');

class Config
{
    protected $data;

    public function __construct($data = null) {
        $this->data = $data;
    }

    public function __get($name) {
        return isset($this->data[$name]) ? new Config($this->data[$name]) : new Config();
    }

    public function isEmpty() {
        return empty($this->data);
    }

    public function get() {
        return $this->data;
    }
}

$test = new Config(array(
    'foo' => array(
        'bar' => array(
            'barfoo' => 1
        )
    )
));


// test 1
if (!$test->foo->bar->isEmpty()) {
    print_r($test->foo->bar->get());
}

// test 2
if (!$test->foo->bar->foobar->isEmpty()) {
    print_r($test->foo->bar->foobar->get());
}

// test 3
if (!$test->foo->bar->barfoo->isEmpty()) {
    print_r($test->foo->bar->barfoo->get());
}

Example:

http://codepad.org/9EZ2Hqf8

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜