File: //usr/local/wp/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php
<?php
namespace WP_CLI\Traverser;
use UnexpectedValueException;
use WP_CLI\Exception\NonExistentKeyException;
class RecursiveDataStructureTraverser {
/**
* @var mixed The data to traverse set by reference.
*/
protected $data;
/**
* @var null|string The key the data belongs to in the parent's data.
*/
protected $key;
/**
* @var null|static The parent instance of the traverser.
*/
protected $parent;
/**
* RecursiveDataStructureTraverser constructor.
*
* @param mixed $data The data to read/manipulate by reference.
* @param string|int $key The key/property the data belongs to.
* @param static|null $parent_instance The parent instance of the traverser.
*/
public function __construct( &$data, $key = null, $parent_instance = null ) {
$this->data =& $data;
$this->key = $key;
$this->parent = $parent_instance;
}
/**
* Get the nested value at the given key path.
*
* @param string|int|array $key_path
*
* @return static
*/
public function get( $key_path ) {
return $this->traverse_to( (array) $key_path )->value();
}
/**
* Get the current data.
*
* @return mixed
*/
public function value() {
return $this->data;
}
/**
* Update a nested value at the given key path.
*
* @param string|int|array $key_path
* @param mixed $value
*/
public function update( $key_path, $value ) {
$this->traverse_to( (array) $key_path )->set_value( $value );
}
/**
* Update the current data with the given value.
*
* This will mutate the variable which was passed into the constructor
* as the data is set and traversed by reference.
*
* @param mixed $value
*/
public function set_value( $value ) {
$this->data = $value;
}
/**
* Unset the value at the given key path.
*
* @param $key_path
*/
public function delete( $key_path ) {
$this->traverse_to( (array) $key_path )->unset_on_parent();
}
/**
* Define a nested value while creating keys if they do not exist.
*
* @param array $key_path
* @param mixed $value
*/
public function insert( $key_path, $value ) {
try {
$this->update( $key_path, $value );
} catch ( NonExistentKeyException $exception ) {
$exception->get_traverser()->create_key();
$this->insert( $key_path, $value );
}
}
/**
* Delete the key on the parent's data that references this data.
*/
public function unset_on_parent() {
$this->parent->delete_by_key( $this->key );
}
/**
* Delete the given key from the data.
*
* @param $key
*/
public function delete_by_key( $key ) {
if ( is_array( $this->data ) ) {
unset( $this->data[ $key ] );
} else {
unset( $this->data->$key );
}
}
/**
* Get an instance of the traverser for the given hierarchical key.
*
* @param array $key_path Hierarchical key path within the current data to traverse to.
*
* @throws NonExistentKeyException
*
* @return self
*/
public function traverse_to( array $key_path ) {
$current = array_shift( $key_path );
if ( null === $current ) {
return $this;
}
if ( ! $this->exists( $current ) ) {
$exception = new NonExistentKeyException( "No data exists for key \"{$current}\"" );
$exception->set_traverser( new self( $this->data, $current, $this->parent ) );
throw $exception;
}
foreach ( $this->data as $key => &$key_data ) {
if ( $key === $current ) {
$traverser = new self( $key_data, $key, $this );
return $traverser->traverse_to( $key_path );
}
}
}
/**
* Create the key on the current data.
*
* @throws UnexpectedValueException
*/
protected function create_key() {
if ( is_array( $this->data ) ) {
$this->data[ $this->key ] = null;
} elseif ( is_object( $this->data ) ) {
$this->data->{$this->key} = null;
} else {
$type = gettype( $this->data );
throw new UnexpectedValueException(
"Cannot create key \"{$this->key}\" on data type {$type}"
);
}
}
/**
* Check if the given key exists on the current data.
*
* @param string $key
*
* @return bool
*/
public function exists( $key ) {
return ( is_array( $this->data ) && array_key_exists( $key, $this->data ) ) ||
( is_object( $this->data ) && property_exists( $this->data, $key ) );
}
}