1: <?php
2:
3: namespace Charcoal\Config;
4:
5: use InvalidArgumentException;
6:
7: /**
8: * Provides an object with the ability to perform lookups into multi-dimensional arrays.
9: *
10: * This is a full implementation of {@see SeparatorAwareInterface}.
11: */
12: trait SeparatorAwareTrait
13: {
14: /**
15: * Token for accessing nested data.
16: *
17: * Is empty by default (which disables the separator feature).
18: *
19: * @var string
20: */
21: protected $separator = '';
22:
23: /**
24: * Sets the token for traversing a data-tree.
25: *
26: * @param string $separator The single-character token to delimit nested data.
27: * If the token is an empty string, data-tree traversal is disabled.
28: * @throws InvalidArgumentException If the $separator is invalid.
29: * @return self
30: */
31: final public function setSeparator($separator)
32: {
33: if (!is_string($separator)) {
34: throw new InvalidArgumentException(
35: 'Separator must be a string'
36: );
37: }
38:
39: if (strlen($separator) > 1) {
40: throw new InvalidArgumentException(
41: 'Separator must be one-character, or empty'
42: );
43: }
44:
45: $this->separator = $separator;
46: return $this;
47: }
48:
49: /**
50: * Gets the token for traversing a data-tree, if any.
51: *
52: * @return string
53: */
54: final public function separator()
55: {
56: return $this->separator;
57: }
58:
59: /**
60: * Determines if this store contains the key-path and if its value is not NULL.
61: *
62: * Traverses each node in the key-path until the endpoint is located.
63: *
64: * @param string $key The key-path to check.
65: * @return boolean TRUE if $key exists and has a value other than NULL, FALSE otherwise.
66: */
67: final protected function hasWithSeparator($key)
68: {
69: $structure = $this;
70: $splitKeys = explode($this->separator, $key);
71: foreach ($splitKeys as $key) {
72: if (!isset($structure[$key])) {
73: return false;
74: }
75: if (!is_array($structure[$key])) {
76: return true;
77: }
78: $structure = $structure[$key];
79: }
80: return true;
81: }
82:
83: /**
84: * Returns the value from the key-path found on this object.
85: *
86: * Traverses each node in the key-path until the endpoint is located.
87: *
88: * @param string $key The key-path to retrieve.
89: * @return mixed Value of the requested $key on success, NULL if the $key is not set.
90: */
91: final protected function getWithSeparator($key)
92: {
93: $structure = $this;
94: $splitKeys = explode($this->separator, $key);
95: foreach ($splitKeys as $key) {
96: if (!isset($structure[$key])) {
97: return null;
98: }
99: if (!is_array($structure[$key])) {
100: return $structure[$key];
101: }
102: $structure = $structure[$key];
103: }
104: return $structure;
105: }
106:
107: /**
108: * Assign a value to the key-path, replacing / merging existing data with the same endpoint.
109: *
110: * Traverses, in reverse, each node in the key-path from the endpoint.
111: *
112: * @param string $key The key-path to assign $value to.
113: * @param mixed $value The data value to assign to $key.
114: * @return void
115: */
116: final protected function setWithSeparator($key, $value)
117: {
118: $structure = $value;
119: $splitKeys = array_reverse(explode($this->separator, $key));
120: foreach ($splitKeys as $key) {
121: $structure = [
122: $key => $structure
123: ];
124: }
125:
126: if (isset($this[$key])) {
127: $data = $this[$key];
128: if (is_array($data)) {
129: $structure[$key] = array_replace_recursive($data, $structure[$key]);
130: }
131: }
132:
133: $this[$key] = $structure[$key];
134: }
135: }
136: