PHP:在运行时修改具有未知结构的数组;什么是最优雅的解决方案?

时间:2011-02-24 21:21:14

标签: php arrays data-structures nested

问题

我有一个函数,它接受一个嵌套数组,其中数组的结构和嵌套在运行时是 unknown 。所有已知的是一些目标字段名和一些叶子的期望值。

问题

1)我希望修改这个未知的结构,并且代码仍然可以被其他程序员阅读和理解。什么(如果有的话)解决方案允许我在PHP中做这样的事情?

// Pseudo-code for things I would like to be able to do
// this is kinda like the same thing as XPATH, but for native PHP array

// find *every* fname with value of "Brad" and change it to "Brian"
$mydata->find_all('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');

// find *the first* fave_color and set it to "Green"
$mydata->find('*:fave_color')->get(0)->set_equal_to('Green');

2)如果没有任何东西可以让我这样做,那么至少接近我希望在这里完成的精神有什么东西吗?

SAMPLE ARRAY

$mydata = array(
   'people' => array(
      array('fname'=>'Alice'),
      array('fname'=>'Brad'),
      array('fname'=>'Chris'),
    ),
   'animals' => array(
      array('fname'=>'Dino'),
      array('fname'=>'Lassie'),
      array('fname'=>'Brad'),
    ),
    'settings' => array(
      'user_prefs'=>array(
        'localhost'=>array(
          'fave_color'=>'blue',
        ),
      ),
    ),
    'places' => array(
      array('state'=>'New york',
            'cities'=>array(
              'name'=>'Albany',
              'name'=>'Buffalo',
              'name'=>'Corning',
            ),
            'state'=>'California',
            'cities'=>array(
              'name'=>'Anaheim',
              'name'=>'Bakersfield',
              'name'=>'Carlsbad',
            ),            
      ),
    ),
);

2 个答案:

答案 0 :(得分:1)

当然没有本机解决方案,语法相当奇怪。如果您希望代码“可读并且易于被其他程序员理解”,请坚持使用我们习惯使用的方法;)

foreach ($mydata as $type => &$data) {
    foreach ($data as &$member) {
        if (isset($member['fname']) && $member['fname'] == 'Brad') {
            $member['fname'] = 'Brian';
        }
    }
}

诚然,它更加冗长,但混淆的可能性要小得多。

答案 1 :(得分:1)

虽然我认为你应该像我之前的回答那样坚持明确的操纵,但是厌倦和阴谋对我有好处;)

它可能有漏洞(显然缺少文档),但如果你坚持这条路线,它应该让你开始:

class Finder {
    protected $data;

    public function __construct(&$data) {
        if (!is_array($data)) {
            throw new InvalidArgumentException;
        }

        $this->data = &$data;
    }

    public function all() {
        return $this->find();
    }

    public function find($expression = null) {
        if (!isset($expression)) {
            return new Results($this->data);
        }

        $results = array();
        $this->_find(explode(':', $expression), $this->data, $results);
        return new Results($results);
    }

    protected function _find($parts, &$data, &$results) {
        if (!$parts) {
            return;
        }

        $currentParts = $parts;
        $search = array_shift($currentParts);
        if ($wildcard = $search == '*') {
            $search = array_shift($currentParts);
        }

        foreach ($data as $key => &$value) {
            if ($key === $search) {
                if ($currentParts) {
                    $this->_find($currentParts, $value, $results);
                } else {
                    $results[] = &$value;
                }
            } else if ($wildcard && is_array($value)) {
                $this->_find($parts, $value, $results);
            }
        }
    }
}

class Results {
    protected $data;

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

    public function get($index, $limit = 1) {
        $this->data = array_slice($this->data, $index, $limit);
        return $this;
    }

    public function set_equal_to($value) {
        foreach ($this->data as &$datum) {
            $datum = $value;
        }
    }

    public function __call($method, $args) {
        if (!preg_match('/^where_?(key|value)_?(eq|contains)$/i', $method, $m)) {
            throw new BadFunctionCallException;
        }

        if (!isset($args[0])) {
            throw new InvalidArgumentException;
        }

        $operand = $args[0];
        $isKey = strtolower($m[1]) == 'key';
        $method = array('Results', '_compare' . (strtolower($m[2]) == 'eq' ? 'EqualTo' : 'Contains'));

        $ret = array();
        foreach ($this->data as $key => &$datum) {
            if (call_user_func($method, $isKey ? $key : $datum, $operand)) {
                $ret[] = &$datum;
            }
        }

        $this->data = $ret;
        return $this;
    }

    protected function _compareEqualTo($value, $test) {
        return $value == $test;
    }

    protected function _compareContains($value, $test) {
        return strpos($value, $test) !== false;
    }
}

$finder = new Finder($mydata);
$finder->find('*:fname')->where_value_eq('Brad')->set_equal_to('Brian');
$finder->find('*:fave_color')->get(0)->set_equal_to('Green');
$finder->find('places:*:cities:*:name')->where_value_contains('ba')->set_equal_to('Stackoton');

print_r($mydata);