PHP: Modifying array with unknown structure at runtime; what is the most elegant solution?

偶尔善良 提交于 2019-12-07 07:55:30

Although I maintain that you should stick with explicit manipulation as in my previous answer, boredom and intrigue got the better of me ;)

It probably has holes (and clearly lacks docs) but if you insist on this route, it should get you started:

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

There's certainly no native solution for this and the syntax is rather strange. If you want the code to "be readable and easily understood by fellow programmers" please stick to methods that we're used to working with ;)

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

It's admittedly more verbose, but there's much less chance of confusion.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!