Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property chaining and isset in configuration object [duplicate]

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?

like image 400
manbeardpig Avatar asked Oct 18 '25 19:10

manbeardpig


2 Answers

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

like image 64
Vladan Avatar answered Oct 20 '25 10:10

Vladan


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.

like image 35
ZoolWay Avatar answered Oct 20 '25 10:10

ZoolWay



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!