PHP: изменение массива с неизвестной структурой во время выполнения; какое самое элегантное решение? - PullRequest
1 голос
/ 25 февраля 2011

ПРОБЛЕМА

У меня есть функция, которая принимает вложенный массив, где структура и вложенность массива неизвестно во время выполнения.Все, что известно, это некоторые из целевых имен полей и желаемых значений некоторых листьев.

ВОПРОСЫ

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) Если нет ничего, что позволило бы мне сделать это, есть ли что-то, что-нибудь, что по крайней мереприближается к духу того, чего я надеюсь достичь здесь?

SAMRLE 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 ]

1 голос
/ 25 февраля 2011

Хотя я утверждаю, что вы должны придерживаться явных манипуляций, как в моем предыдущем ответе, скука и интрига одолели меня;)

Возможно, у него есть дыры (и явно нет документов), но если вы настаиваете на этом маршруте, вам следует начать:

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);
1 голос
/ 25 февраля 2011

Конечно, для этого нет нативного решения, и синтаксис довольно странный. Если вы хотите, чтобы код был «читаемым и понятным для других программистов», пожалуйста, придерживайтесь методов, с которыми мы привыкли работать;)

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

По общему признанию, это более многословно, но вероятность путаницы гораздо меньше.

...