Skip to content

Commit d212c2e

Browse files
petitphpschlessera
authored andcommitted
Add RecursiveDataStructureTraverser helper to manipulate nested data
1 parent 08f7887 commit d212c2e

File tree

3 files changed

+228
-0
lines changed

3 files changed

+228
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace WP_CLI\Cache;
4+
5+
use OutOfBoundsException;
6+
7+
class NonExistentKeyException extends OutOfBoundsException {
8+
/** @var RecursiveDataStructureTraverser */
9+
protected $traverser;
10+
11+
/**
12+
* @param RecursiveDataStructureTraverser $traverser
13+
*/
14+
public function set_traverser( $traverser ) {
15+
$this->traverser = $traverser;
16+
}
17+
18+
/**
19+
* @return RecursiveDataStructureTraverser
20+
*/
21+
public function get_traverser() {
22+
return $this->traverser;
23+
}
24+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php
2+
3+
namespace WP_CLI\Cache;
4+
5+
use UnexpectedValueException;
6+
7+
class RecursiveDataStructureTraverser {
8+
9+
/**
10+
* @var mixed The data to traverse set by reference.
11+
*/
12+
protected $data;
13+
14+
/**
15+
* @var null|string The key the data belongs to in the parent's data.
16+
*/
17+
protected $key;
18+
19+
/**
20+
* @var null|static The parent instance of the traverser.
21+
*/
22+
protected $parent;
23+
24+
/**
25+
* RecursiveDataStructureTraverser constructor.
26+
*
27+
* @param mixed $data The data to read/manipulate by reference.
28+
* @param string|int $key The key/property the data belongs to.
29+
* @param static $parent
30+
*/
31+
public function __construct( &$data, $key = null, $parent = null ) {
32+
$this->data =& $data;
33+
$this->key = $key;
34+
$this->parent = $parent;
35+
}
36+
37+
/**
38+
* Get the nested value at the given key path.
39+
*
40+
* @param string|int|array $key_path
41+
*
42+
* @return static
43+
*/
44+
public function get( $key_path ) {
45+
return $this->traverse_to( (array) $key_path )->value();
46+
}
47+
48+
/**
49+
* Get the current data.
50+
*
51+
* @return mixed
52+
*/
53+
public function value() {
54+
return $this->data;
55+
}
56+
57+
/**
58+
* Update a nested value at the given key path.
59+
*
60+
* @param string|int|array $key_path
61+
* @param mixed $value
62+
*/
63+
public function update( $key_path, $value ) {
64+
$this->traverse_to( (array) $key_path )->set_value( $value );
65+
}
66+
67+
/**
68+
* Update the current data with the given value.
69+
*
70+
* This will mutate the variable which was passed into the constructor
71+
* as the data is set and traversed by reference.
72+
*
73+
* @param mixed $value
74+
*/
75+
public function set_value( $value ) {
76+
$this->data = $value;
77+
}
78+
79+
/**
80+
* Unset the value at the given key path.
81+
*
82+
* @param $key_path
83+
*/
84+
public function delete( $key_path ) {
85+
$this->traverse_to( (array) $key_path )->unset_on_parent();
86+
}
87+
88+
/**
89+
* Define a nested value while creating keys if they do not exist.
90+
*
91+
* @param array $key_path
92+
* @param mixed $value
93+
*/
94+
public function insert( $key_path, $value ) {
95+
try {
96+
$this->update( $key_path, $value );
97+
} catch ( NonExistentKeyException $exception ) {
98+
$exception->get_traverser()->create_key();
99+
$this->insert( $key_path, $value );
100+
}
101+
}
102+
103+
/**
104+
* Delete the key on the parent's data that references this data.
105+
*/
106+
public function unset_on_parent() {
107+
$this->parent->delete_by_key( $this->key );
108+
}
109+
110+
/**
111+
* Delete the given key from the data.
112+
*
113+
* @param $key
114+
*/
115+
public function delete_by_key( $key ) {
116+
if ( is_array( $this->data ) ) {
117+
unset( $this->data[ $key ] );
118+
} else {
119+
unset( $this->data->$key );
120+
}
121+
}
122+
123+
/**
124+
* Get an instance of the traverser for the given hierarchical key.
125+
*
126+
* @param array $key_path Hierarchical key path within the current data to traverse to.
127+
*
128+
* @throws NonExistentKeyException
129+
*
130+
* @return static
131+
*/
132+
public function traverse_to( array $key_path ) {
133+
$current = array_shift( $key_path );
134+
135+
if ( null === $current ) {
136+
return $this;
137+
}
138+
139+
if ( ! $this->exists( $current ) ) {
140+
$exception = new NonExistentKeyException( "No data exists for key \"{$current}\"" );
141+
$exception->set_traverser( new static( $this->data, $current, $this->parent ) );
142+
throw $exception;
143+
}
144+
145+
foreach ( $this->data as $key => &$key_data ) {
146+
if ( $key === $current ) {
147+
$traverser = new static( $key_data, $key, $this );
148+
return $traverser->traverse_to( $key_path );
149+
}
150+
}
151+
}
152+
153+
/**
154+
* Create the key on the current data.
155+
*
156+
* @throws UnexpectedValueException
157+
*/
158+
protected function create_key() {
159+
if ( is_array( $this->data ) ) {
160+
$this->data[ $this->key ] = null;
161+
} elseif ( is_object( $this->data ) ) {
162+
$this->data->{$this->key} = null;
163+
} else {
164+
$type = gettype( $this->data );
165+
throw new UnexpectedValueException(
166+
"Cannot create key \"{$this->key}\" on data type {$type}"
167+
);
168+
}
169+
}
170+
171+
/**
172+
* Check if the given key exists on the current data.
173+
*
174+
* @param string $key
175+
*
176+
* @return bool
177+
*/
178+
public function exists( $key ) {
179+
return ( is_array( $this->data ) && array_key_exists( $key, $this->data ) ) ||
180+
( is_object( $this->data ) && property_exists( $this->data, $key ) );
181+
}
182+
}

src/Cache/Utils.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace WP_CLI\Cache;
4+
5+
class Utils {
6+
7+
/**
8+
* Check whether any input is passed to STDIN.
9+
*
10+
* @return bool
11+
*/
12+
public static function has_stdin() {
13+
$handle = fopen( 'php://stdin', 'r' );
14+
$read = array( $handle );
15+
$write = null;
16+
$except = null;
17+
$streams = stream_select( $read, $write, $except, 0 );
18+
fclose( $handle );
19+
20+
return 1 === $streams;
21+
}
22+
}

0 commit comments

Comments
 (0)