Overview

Namespaces

  • Charcoal
    • Config

Classes

  • Charcoal\Config\AbstractConfig
  • Charcoal\Config\AbstractEntity
  • Charcoal\Config\GenericConfig

Interfaces

  • Charcoal\Config\ConfigInterface
  • Charcoal\Config\ConfigurableInterface
  • Charcoal\Config\DelegatesAwareInterface
  • Charcoal\Config\EntityInterface
  • Charcoal\Config\FileAwareInterface
  • Charcoal\Config\SeparatorAwareInterface

Traits

  • Charcoal\Config\ConfigurableTrait
  • Charcoal\Config\DelegatesAwareTrait
  • Charcoal\Config\FileAwareTrait
  • Charcoal\Config\SeparatorAwareTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\Config;
  4: 
  5: use ArrayIterator;
  6: use IteratorAggregate;
  7: use Traversable;
  8: use InvalidArgumentException;
  9: 
 10: // From PSR-11
 11: use Psr\Container\ContainerInterface;
 12: 
 13: /**
 14:  * Default configuration container / registry.
 15:  *
 16:  * ### Notes on {@see SeparatorAwareTrait}:
 17:  *
 18:  * - Provides the ability for a store to fetch data that is nested in a tree-like structure,
 19:  *   often referred to as "dot" notation.
 20:  *
 21:  * ### Notes on {@see DelegatesAwareTrait}:
 22:  *
 23:  * - Provides the ability for a store to fetch data in another store.
 24:  * - Provides this store with a way to register one or more delegate stores.
 25:  */
 26: abstract class AbstractConfig extends AbstractEntity implements
 27:     ConfigInterface,
 28:     ContainerInterface,
 29:     IteratorAggregate
 30: {
 31:     use DelegatesAwareTrait;
 32:     use FileAwareTrait;
 33:     use SeparatorAwareTrait;
 34: 
 35:     const DEFAULT_SEPARATOR = '.';
 36: 
 37:     /**
 38:      * Create the configuration.
 39:      *
 40:      * @param  mixed             $data      Initial data. Either a filepath,
 41:      *     an associative array, or an {@see Traversable iterable object}.
 42:      * @param  EntityInterface[] $delegates An array of delegates (config) to set.
 43:      * @throws InvalidArgumentException If $data is invalid.
 44:      */
 45:     final public function __construct($data = null, array $delegates = null)
 46:     {
 47:         // Always set the default chaining notation
 48:         $this->setSeparator(self::DEFAULT_SEPARATOR);
 49: 
 50:         // Always set the default data first.
 51:         $this->setData($this->defaults());
 52: 
 53:         // Set the delegates, if necessary.
 54:         if (isset($delegates)) {
 55:             $this->setDelegates($delegates);
 56:         }
 57: 
 58:         if ($data === null) {
 59:             return;
 60:         }
 61: 
 62:         if (is_string($data)) {
 63:             // Treat the parameter as a filepath
 64:             $this->addFile($data);
 65:         } elseif (is_array($data)) {
 66:             $this->merge($data);
 67:         } elseif ($data instanceof Traversable) {
 68:             $this->merge($data);
 69:         } else {
 70:             throw new InvalidArgumentException(sprintf(
 71:                 'Data must be a config file, an associative array, or an object implementing %s',
 72:                 Traversable::class
 73:             ));
 74:         }
 75:     }
 76: 
 77:     /**
 78:      * Gets all default data from this store.
 79:      *
 80:      * Pre-populates new stores.
 81:      *
 82:      * May be reimplemented in inherited classes if any default values should be defined.
 83:      *
 84:      * @return array Key-value array of data
 85:      */
 86:     public function defaults()
 87:     {
 88:         return [];
 89:     }
 90: 
 91:     /**
 92:      * Adds new data, replacing / merging existing data with the same key.
 93:      *
 94:      * @uses   self::offsetReplace()
 95:      * @param  array|Traversable $data Key-value dataset to merge.
 96:      *     Either an associative array or an {@see Traversable iterable object}
 97:      *     (such as {@see ConfigInterface}).
 98:      * @return self
 99:      */
100:     public function merge($data)
101:     {
102:         foreach ($data as $key => $value) {
103:             $this->offsetReplace($key, $value);
104:         }
105:         return $this;
106:     }
107: 
108:     /**
109:      * Create a new iterator from the configuration instance.
110:      *
111:      * @see    IteratorAggregate
112:      * @return ArrayIterator
113:      */
114:     public function getIterator()
115:     {
116:         return new ArrayIterator($this->data());
117:     }
118: 
119:     /**
120:      * Determines if this store contains the specified key and if its value is not NULL.
121:      *
122:      * Routine:
123:      * - If the data key is {@see SeparatorAwareTrait::$separator nested},
124:      *   the data-tree is traversed until the endpoint is found, if any;
125:      * - If the data key does NOT exist on the store, a lookup is performed
126:      *   on each delegate store until a key is found, if any.
127:      *
128:      * @see    \ArrayAccess
129:      * @uses   SeparatorAwareTrait::hasWithSeparator()
130:      * @uses   DelegatesAwareTrait::hasInDelegates()
131:      * @param  string $key The data key to check.
132:      * @throws InvalidArgumentException If the $key is not a string or is a numeric value.
133:      * @return boolean TRUE if $key exists and has a value other than NULL, FALSE otherwise.
134:      */
135:     public function offsetExists($key)
136:     {
137:         if (is_numeric($key)) {
138:             throw new InvalidArgumentException(
139:                 'Entity array access only supports non-numeric keys'
140:             );
141:         }
142: 
143:         if ($this->separator && strstr($key, $this->separator)) {
144:             return $this->hasWithSeparator($key);
145:         }
146: 
147:         $key = $this->camelize($key);
148: 
149:         /** @internal Edge Case: "_" → "" */
150:         if ($key === '') {
151:             return false;
152:         }
153: 
154:         if (is_callable([ $this, $key ])) {
155:             $value = $this->{$key}();
156:         } else {
157:             if (!isset($this->{$key})) {
158:                 return $this->hasInDelegates($key);
159:             }
160:             $value = $this->{$key};
161:         }
162: 
163:         return ($value !== null);
164:     }
165: 
166:     /**
167:      * Returns the value from the specified key on this entity.
168:      *
169:      * Routine:
170:      * - If the data key is {@see SeparatorAwareTrait::$separator nested},
171:      *   the data-tree is traversed until the endpoint to return its value, if any;
172:      * - If the data key does NOT exist on the store, a lookup is performed
173:      *   on each delegate store until a value is found, if any.
174:      *
175:      * @see    \ArrayAccess
176:      * @uses   SeparatorAwareTrait::getWithSeparator()
177:      * @uses   DelegatesAwareTrait::getInDelegates()
178:      * @param  string $key The data key to retrieve.
179:      * @throws InvalidArgumentException If the $key is not a string or is a numeric value.
180:      * @return mixed Value of the requested $key on success, NULL if the $key is not set.
181:      */
182:     public function offsetGet($key)
183:     {
184:         if (is_numeric($key)) {
185:             throw new InvalidArgumentException(
186:                 'Entity array access only supports non-numeric keys'
187:             );
188:         }
189: 
190:         if ($this->separator && strstr($key, $this->separator)) {
191:             return $this->getWithSeparator($key);
192:         }
193: 
194:         $key = $this->camelize($key);
195: 
196:         /** @internal Edge Case: "_" → "" */
197:         if ($key === '') {
198:             return null;
199:         }
200: 
201:         if (is_callable([ $this, $key ])) {
202:             return $this->{$key}();
203:         } else {
204:             if (isset($this->{$key})) {
205:                 return $this->{$key};
206:             } else {
207:                 return $this->getInDelegates($key);
208:             }
209:         }
210:     }
211: 
212:     /**
213:      * Assigns the value to the specified key on this entity.
214:      *
215:      * Routine:
216:      * - If the data key is {@see SeparatorAwareTrait::$separator nested},
217:      *   the data-tree is traversed until the endpoint to assign its value;
218:      *
219:      * @see    \ArrayAccess
220:      * @uses   SeparatorAwareTrait::setWithSeparator()
221:      * @param  string $key   The data key to assign $value to.
222:      * @param  mixed  $value The data value to assign to $key.
223:      * @throws InvalidArgumentException If the $key is not a string or is a numeric value.
224:      * @return void
225:      */
226:     public function offsetSet($key, $value)
227:     {
228:         if (is_numeric($key)) {
229:             throw new InvalidArgumentException(
230:                 'Entity array access only supports non-numeric keys'
231:             );
232:         }
233: 
234:         if ($this->separator && strstr($key, $this->separator)) {
235:             $this->setWithSeparator($key, $value);
236:             return;
237:         }
238: 
239:         $key = $this->camelize($key);
240: 
241:         /** @internal Edge Case: "_" → "" */
242:         if ($key === '') {
243:             return;
244:         }
245: 
246:         $setter = 'set'.ucfirst($key);
247:         if (is_callable([ $this, $setter ])) {
248:             $this->{$setter}($value);
249:         } else {
250:             $this->{$key} = $value;
251:         }
252: 
253:         $this->keys[$key] = true;
254:     }
255: 
256:     /**
257:      * Replaces the value from the specified key.
258:      *
259:      * Routine:
260:      * - When the value in the Config and the new value are both arrays,
261:      *   the method will replace their respective value recursively.
262:      * - Then or otherwise, the new value is {@see self::offsetSet() assigned} to the Config.
263:      *
264:      * @uses   self::offsetSet()
265:      * @uses   array_replace_recursive()
266:      * @param  string $key   The data key to assign or merge $value to.
267:      * @param  mixed  $value The data value to assign to or merge with $key.
268:      * @throws InvalidArgumentException If the $key is not a string or is a numeric value.
269:      * @return void
270:      */
271:     public function offsetReplace($key, $value)
272:     {
273:         if (is_numeric($key)) {
274:             throw new InvalidArgumentException(
275:                 'Entity array access only supports non-numeric keys'
276:             );
277:         }
278: 
279:         $key = $this->camelize($key);
280: 
281:         /** @internal Edge Case: "_" → "" */
282:         if ($key === '') {
283:             return;
284:         }
285: 
286:         if (is_array($value) && isset($this[$key])) {
287:             $data = $this[$key];
288:             if (is_array($data)) {
289:                 $value = array_replace_recursive($data, $value);
290:             }
291:         }
292: 
293:         $this[$key] = $value;
294:     }
295: 
296:     /**
297:      * Adds a configuration file to the configset.
298:      *
299:      * Natively supported file formats: INI, JSON, PHP.
300:      *
301:      * @uses   FileAwareTrait::loadFile()
302:      * @param  string $path The file to load and add.
303:      * @return self
304:      */
305:     public function addFile($path)
306:     {
307:         $config = $this->loadFile($path);
308:         if (is_array($config)) {
309:             $this->merge($config);
310:         }
311:         return $this;
312:     }
313: }
314: 
API documentation generated by ApiGen