1: <?php
2:
3: /*
4: * The MIT License
5: *
6: * Copyright 2016 David Schoenbauer <dschoenbauer@gmail.com>.
7: *
8: * Permission is hereby granted, free of charge, to any person obtaining a copy
9: * of this software and associated documentation files (the "Software"), to deal
10: * in the Software without restriction, including without limitation the rights
11: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12: * copies of the Software, and to permit persons to whom the Software is
13: * furnished to do so, subject to the following conditions:
14: *
15: * The above copyright notice and this permission notice shall be included in
16: * all copies or substantial portions of the Software.
17: *
18: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23: * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24: * THE SOFTWARE.
25: */
26:
27: namespace DSchoenbauer\DotNotation;
28:
29: use DSchoenbauer\DotNotation\Exception\PathNotArrayException;
30: use DSchoenbauer\DotNotation\Exception\PathNotFoundException;
31: use DSchoenbauer\DotNotation\Exception\TargetNotArrayException;
32: use DSchoenbauer\DotNotation\Exception\UnexpectedValueException;
33:
34: /**
35: * An easier way to deal with complex PHP arrays
36: *
37: * @author David Schoenbauer
38: * @version 1.1.1
39: */
40: class ArrayDotNotation {
41:
42: /**
43: * Property that houses the data that the dot notation should access
44: * @var array
45: */
46: private $_data = [];
47: private $_notationType = ".";
48:
49: /**
50: * Sets the data to parse in a chain
51: * @param array $data optional Array of data that will be accessed via dot notation.
52: * @author John Smart
53: * @author David Schoenbauer
54: * @return \static
55: */
56: public static function with(array $data = []) {
57: return new static($data);
58: }
59:
60: /**
61: * An alias for setData
62: *
63: * @see ArrayDotNotation::setData()
64: * @since 1.0.0
65: * @param array $data optional Array of data that will be accessed via dot notation.
66: */
67: public function __construct(array $data = []) {
68: $this->setData($data);
69: }
70:
71: /**
72: * returns the array
73: *
74: * returns the array that the dot notation has been used on.
75: *
76: * @since 1.0.0
77: * @return array Array of data that will be accessed via dot notation.
78: */
79: public function getData() {
80: return $this->_data;
81: }
82:
83: /**
84: * sets the array that the dot notation will be used on.
85: *
86: * @since 1.0.0
87: * @param array $data Array of data that will be accessed via dot notation.
88: * @return $this
89: */
90: public function setData(array $data) {
91: $this->_data = $data;
92: return $this;
93: }
94:
95: /**
96: * Retrieves a value from an array structure as defined by a dot notation string
97: *
98: * Returns a value from an array.
99: *
100: * @since 1.0.0
101: * @param string $dotNotation a string of keys concatenated together by a dot that represent different levels of an array
102: * @param mixed $defaultValue value to return if the dot notation does not find a valid key
103: * @return mixed value found via dot notation in the array of data
104: */
105: public function get($dotNotation, $defaultValue = null) {
106: return $this->recursiveGet($this->getData(), $this->getKeys($dotNotation), $defaultValue);
107: }
108:
109: /**
110: * Recursively works though an array level by level until the final key is found. Once key is found the keys value is returned.
111: * @since 1.0.0
112: * @param array $data array that has value to be retrieved
113: * @param array $keys array of keys for each level of the array
114: * @param mixed $defaultValue value to return when a key is not found
115: * @return mixed value that the keys find in the data array
116: */
117: protected function recursiveGet($data, $keys, $defaultValue) {
118: $key = array_shift($keys);
119: if (is_array($data) && $key && count($keys) == 0) { //Last Key
120: return array_key_exists($key, $data) ? $data[$key] : $defaultValue;
121: } elseif (is_array($data) && array_key_exists($key, $data)) {
122: return $this->recursiveGet($data[$key], $keys, $defaultValue);
123: }
124: return $defaultValue;
125: }
126:
127: /**
128: * sets a value into a complicated array data structure
129: *
130: * Places a value into a tiered array. A dot notation of "one.two.three"
131: * would place the value at ['one' => ['two' => ['three' => 'valueHere' ]]]
132: * If the array does not exist it will be added. Indexed arrays can be used,
133: * and referenced by number. i.e. "one.0" would return the first item of the
134: * one array.
135: *
136: * @since 1.0.0
137: * @param string $dotNotation dot notation representation of keys of where to set a value
138: * @param mixed $value any value to be stored with in a key structure of dot notation
139: * @return $this
140: * @throws PathNotArrayException if a value in the dot notation path is not an array
141: */
142: public function set($dotNotation, $value) {
143: $this->recursiveSet($this->_data, $this->getKeys($dotNotation), $value);
144: return $this;
145: }
146:
147: /**
148: * Transverses the keys array until it reaches the appropriate key in the data array and sets that key to the value.
149: * If the keys don't exist they are created.
150: *
151: * @since 1.0.0
152: * @param array $data data to be traversed
153: * @param array $keys the remaining keys of focus for the data array
154: * @param mixed $value the value to be placed at the final key
155: * @throws PathNotArrayException if a value in the dot notation path is not an array
156: */
157: protected function recursiveSet(array &$data, array $keys, $value) {
158: $key = array_shift($keys);
159: if ($key && count($keys) == 0) { //Last Key
160: $data[$key] = $value;
161: } else {
162: if (!array_key_exists($key, $data)) {
163: $data[$key] = [];
164: } elseif (!is_array($data[$key])) {
165: throw new Exception\PathNotArrayException($key);
166: }
167: $this->recursiveSet($data[$key], $keys, $value);
168: }
169: }
170:
171: /**
172: * Merges two arrays together over writing existing values with new values, while adding new array structure to the data
173: *
174: * @since 1.1.0
175: * @param string $dotNotation dot notation representation of keys of where to set a value
176: * @param array $value array to be merged with an existing array
177: * @return $this
178: * @throws UnexpectedValueException if a value in the dot notation path is not an array
179: * @throws TargetNotArrayException if the value in the dot notation target is not an array
180: */
181: public function merge($dotNotation, array $value) {
182: $target = $this->get($dotNotation, []);
183: if (!is_array($target)) {
184: throw new Exception\TargetNotArrayException($dotNotation);
185: }
186: $this->set($dotNotation, array_merge_recursive($target, $value));
187: return $this;
188: }
189:
190: /**
191: * Removes data from the array
192: *
193: * @since 1.1.0
194: * @param string $dotNotation dot notation representation of keys of where to remove a value
195: * @return $this
196: */
197: public function remove($dotNotation) {
198: $this->recursiveRemove($this->_data, $this->getKeys($dotNotation));
199: return $this;
200: }
201:
202: /**
203: * Transverses the keys array until it reaches the appropriate key in the
204: * data array and then deletes the value from the key.
205: *
206: * @since 1.1.0
207: * @param array $data data to be traversed
208: * @param array $keys the remaining keys of focus for the data array
209: * @throws UnexpectedValueException if a value in the dot notation path is
210: * not an array
211: */
212: protected function recursiveRemove(array &$data, array $keys) {
213: $key = array_shift($keys);
214: if (!array_key_exists($key, $data)) {
215: throw new PathNotFoundException($key);
216: } elseif ($key && count($keys) == 0) { //Last Key
217: unset($data[$key]);
218: } elseif (!is_array($data[$key])) {
219: throw new PathNotArrayException($key);
220: } else {
221: $this->recursiveRemove($data[$key], $keys);
222: }
223: }
224:
225: /**
226: * consistently parses notation keys
227: * @param type $notation key path to a value in an array
228: * @return array array of keys as delimited by the notation type
229: */
230: protected function getKeys($notation) {
231: return explode($this->getNotationType(), $notation);
232: }
233:
234: /**
235: * Returns the current notation type that delimits the notation path.
236: * Default: "."
237: * @return string current notation character delimiting the notation path
238: */
239: public function getNotationType() {
240: return $this->_notationType;
241: }
242:
243: /**
244: * Sets the current notation type used to delimit the notation path.
245: * @param string $notationType
246: * @return $this
247: */
248: public function setNotationType($notationType = ".") {
249: $this->_notationType = $notationType;
250: return $this;
251: }
252:
253: /**
254: * Checks to see if a dot notation path is present in the data set.
255: * @param string $dotNotation dot notation representation of keys of where to remove a value
256: * @return boolean returns true if the do notation path exists in the data
257: */
258: public function has($dotNotation) {
259: $keys = $this->getKeys($dotNotation);
260: $dataRef = &$this->_data;
261: foreach ($keys as $key) {
262: if (!is_array($dataRef) || !array_key_exists($key, $dataRef)) {
263: return false;
264: } else {
265: $dataRef = &$dataRef[$key];
266: }
267: }
268: return true;
269: }
270:
271: }
272: