Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Move RecursiveDataStructureTraverser from wp-cli/entity to wp-cli/w…
…p-cli package
  • Loading branch information
thelovekesh committed Nov 10, 2023
commit a085fa0d67bba220cd3aa2f79365c265eaef6f51
183 changes: 183 additions & 0 deletions php/WP_CLI/Traversers/RecursiveDataStructureTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

namespace WP_CLI\Traversers;

use UnexpectedValueException;
use WP_CLI\Exceptions\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 static
*/
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 static( $this->data, $current, $this->parent ) );
throw $exception;
}

foreach ( $this->data as $key => &$key_data ) {
if ( $key === $current ) {
$traverser = new static( $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 ) );
}
}