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.0.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:
48: /**
49: * An alias for setData
50: *
51: * @see ArrayDotNotation::setData()
52: * @since 1.0.0
53: * @param array $data Array of data that will be accessed via dot notation.
54: */
55: public function __construct(array $data = []) {
56: $this->setData($data);
57: }
58:
59: /**
60: * returns the array
61: *
62: * returns the array that the dot notation has been used on.
63: *
64: * @since 1.0.0
65: * @return array Array of data that will be accessed via dot notation.
66: */
67: public function getData() {
68: return $this->_data;
69: }
70:
71: /**
72: * sets the array that the dot notation will be used on.
73: *
74: * @since 1.0.0
75: * @param array $data Array of data that will be accessed via dot notation.
76: * @return $this
77: */
78: public function setData(array $data) {
79: $this->_data = $data;
80: return $this;
81: }
82:
83: /**
84: * Retrieves a value from an array structure as defined by a dot notation string
85: *
86: * Returns a value from an array.
87: *
88: * @since 1.0.0
89: * @param string $dotNotation a string of keys concatenated together by a dot that represent different levels of an array
90: * @param mixed $defaultValue value to return if the dot notation does not find a valid key
91: * @return mixed value found via dot notation in the array of data
92: */
93: public function get($dotNotation, $defaultValue = null) {
94: return $this->recursiveGet($this->getData(), explode('.', $dotNotation), $defaultValue);
95: }
96:
97: /**
98: * Recursively works though an array level by level until the final key is found. Once key is found the keys value is returned.
99: * @since 1.0.0
100: * @param array $data array that has value to be retrieved
101: * @param array $keys array of keys for each level of the array
102: * @param mixed $defaultValue value to return when a key is not found
103: * @return mixed value that the keys find in the data array
104: */
105: protected function recursiveGet($data, $keys, $defaultValue) {
106: $key = array_shift($keys);
107: if (is_array($data) && $key && count($keys) == 0) { //Last Key
108: return array_key_exists($key, $data) ? $data[$key] : $defaultValue;
109: } elseif (is_array($data) && array_key_exists($key, $data)) {
110: return $this->recursiveGet($data[$key], $keys, $defaultValue);
111: }
112: return $defaultValue;
113: }
114:
115: /**
116: * sets a value into a complicated array data structure
117: *
118: * Places a value into a tiered array. A dot notation of "one.two.three"
119: * would place the value at ['one' => ['two' => ['three' => 'valueHere' ]]]
120: * If the array does not exist it will be added. Indexed arrays can be used,
121: * and referenced by number. i.e. "one.0" would return the first item of the
122: * one array.
123: *
124: * @since 1.0.0
125: * @param string $dotNotation dot notation representation of keys of where to set a value
126: * @param mixed $value any value to be stored with in a key structure of dot notation
127: * @return $this
128: * @throws PathNotArrayException if a value in the dot notation path is not an array
129: */
130: public function set($dotNotation, $value) {
131: $this->recursiveSet($this->_data, explode('.', $dotNotation), $value);
132: return $this;
133: }
134:
135: /**
136: * Transverses the keys array until it reaches the appropriate key in the data array and sets that key to the value.
137: * If the keys don't exist they are created.
138: *
139: * @since 1.0.0
140: * @param array $data data to be traversed
141: * @param array $keys the remaining keys of focus for the data array
142: * @param mixed $value the value to be placed at the final key
143: * @throws PathNotArrayException if a value in the dot notation path is not an array
144: */
145: protected function recursiveSet(array &$data, array $keys, $value) {
146: $key = array_shift($keys);
147: if ($key && count($keys) == 0) { //Last Key
148: $data[$key] = $value;
149: } else {
150: if (!array_key_exists($key, $data)) {
151: $data[$key] = [];
152: } elseif (!is_array($data[$key])) {
153: throw new Exception\PathNotArrayException($key);
154: }
155: $this->recursiveSet($data[$key], $keys, $value);
156: }
157: }
158:
159: /**
160: * Merges two arrays together over writing existing values with new values, while adding new array structure to the data
161: *
162: * @since 1.1.0
163: * @param string $dotNotation dot notation representation of keys of where to set a value
164: * @param array $value array to be merged with an existing array
165: * @return $this
166: * @throws UnexpectedValueException if a value in the dot notation path is not an array
167: * @throws TargetNotArrayException if the value in the dot notation target is not an array
168: */
169: public function merge($dotNotation, array $value) {
170: $target = $this->get($dotNotation, []);
171: if (!is_array($target)) {
172: throw new Exception\TargetNotArrayException($dotNotation);
173: }
174: $this->set($dotNotation, array_merge_recursive($target, $value));
175: return $this;
176: }
177:
178: /**
179: * Removes data from the array
180: *
181: * @since 1.1.0
182: * @param string $dotNotation dot notation representation of keys of where to remove a value
183: * @return $this
184: */
185: public function remove($dotNotation) {
186: $this->recursiveRemove($this->_data, explode('.', $dotNotation), $dotNotation);
187: return $this;
188: }
189:
190: /**
191: * Transverses the keys array until it reaches the appropriate key in the data array and then deletes the value from the key.
192: *
193: * @since 1.1.0
194: * @param array $data data to be traversed
195: * @param array $keys the remaining keys of focus for the data array
196: * @throws UnexpectedValueException if a value in the dot notation path is not an array
197: */
198: protected function recursiveRemove(array &$data, array $keys) {
199: $key = array_shift($keys);
200: if (!array_key_exists($key, $data)) {
201: throw new PathNotFoundException($key);
202: } elseif ($key && count($keys) == 0) { //Last Key
203: unset($data[$key]);
204: } elseif (!is_array($data[$key])) {
205: throw new PathNotArrayException($key);
206: } else {
207: $this->recursiveRemove($data[$key], $keys);
208: }
209: }
210:
211: }
212: