属性链接和配置对象中的isset

时间:2011-05-06 06:07:14

标签: php arrays oop configuration chaining

我找不到一个与我相似的问题,但如果你能找到一个让我随时告诉我的问题......

我正在试图找出如何有效地创建一个整洁的配置对象。

我希望对象(或某种类型的配置管理器)能够将数组或INI文件转换为父/子对象分组。

例如:

$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'

我使用以下代码有效地完成了这项工作:

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 ] );       
}

}

问题

大部分工作都很完美,但我在弄清楚如何有效地使用 isset 时遇到了一些麻烦。使用属性链接, isset 仅适用于链中的最后一个值,例如:

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

使用$ config-> somecategory返回的对象并检查它是否包含一个名为'key'的对象

这意味着如果$ config-> somecategory不存在,则抛出异常。用户必须这样做才能有效地检查:

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

但这似乎很烦人。

另一方面,不需要在每个级别检查数组; PHP可以检查整个事情:

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

我正在寻找的是一种实现我的类的方法,所以我可以对待我的对象,就像我对待数组一样:

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

如果'somecategory'不存在,那么就不会抛出异常......

想法?

4 个答案:

答案 0 :(得分:1)

从PHP 7开始,可以为此目的使用a not well documented feature of null coalesce operator

$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

这是一个online example的行动

答案 1 :(得分:0)

不幸的是,没有isset的版本可以正确检查您的属性链。即使编写自己的方法也无济于事,因为如果somecategory已经取消设置,则将链作为参数传递给您的方法已经失败。


您可以实现访问未设置属性的魔术方法(可能在您的配置对象通用的基类中)。这将创建一个类UnsetProperty的虚拟对象并返回该对象。

$config->someCategory->key的课程将为UnsetProperty提供$config->someCategory。此对象还会为UnsetProperty提供新的$obj->key。如果您在IsSet()中实现方法UnsetProperty返回false,而在其他属性中返回true,则只需将您的支票设置为:

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

这需要做很多事情我不确定你是否不喜欢使用链接的isset-calls。

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

取决于样式和您拥有的嵌套级别数。

希望你明白这种可能性背后的想法。

答案 2 :(得分:0)

也许是这样的?

唯一的问题是,您必须调用isEmpty来检查是否给出了配置,并get来获取最终值。 (就像在底部的3个测试案例中可以看到的那样)

<?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());
}

示例:

http://codepad.org/9EZ2Hqf8

答案 3 :(得分:0)

看看Zend_Config。它几乎与您描述的完全一致。

您可以直接使用它,也可以仅仅作为教学指南来构建自己的。