1: <?php
2: namespace Peridot\Leo\ObjectPath;
3:
4: /**
5: * ObjectPath is a utility for parsing object and array strings into
6: * ObjectPathValues.
7: *
8: * @package Peridot\Leo\Utility
9: */
10: class ObjectPath
11: {
12: /**
13: * The subject to match path expressions against.
14: *
15: * @var array|object
16: */
17: protected $subject;
18:
19: /**
20: * A pattern for matching array keys
21: *
22: * @var string
23: */
24: private static $arrayKey = '/\[([^\]]+)\]/';
25:
26: /**
27: * @param array|object $subject
28: */
29: public function __construct($subject)
30: {
31: $this->subject = $subject;
32: }
33:
34: /**
35: * Returns an ObjectPathValue if the property described by $path
36: * can be located in the subject.
37: *
38: * A path expression uses object and array syntax.
39: *
40: * @code
41: *
42: * $person = new stdClass();
43: * $person->name = new stdClass();
44: * $person->name->first = 'brian';
45: * $person->name->last = 'scaturro';
46: * $person->hobbies = ['programming', 'reading', 'board games'];
47: *
48: * $path = new ObjectPath($person);
49: * $first = $path->get('name->first');
50: * $reading = $path->get('hobbies[0]');
51: *
52: * @endcode
53: *
54: * @param string $path
55: * @return ObjectPathValue
56: */
57: public function get($path)
58: {
59: $parts = $this->getPathParts($path);
60: $properties = $this->getPropertyCollection($this->subject);
61: $pathValue = null;
62: while ($properties && $parts) {
63: $key = array_shift($parts);
64: $key = $this->normalizeKey($key);
65: $pathValue = array_key_exists($key, $properties) ? new ObjectPathValue($key, $properties[$key]) : null;
66:
67: if (! array_key_exists($key, $properties)) {
68: break;
69: }
70:
71: $properties = $this->getPropertyCollection($properties[$key]);
72: }
73: return $pathValue;
74: }
75:
76: /**
77: * Breaks a path expression into an array used
78: * for navigating a path.
79: *
80: * @param $path
81: * @return array
82: */
83: public function getPathParts($path)
84: {
85: $path = preg_replace('/\[/', '->[', $path);
86: if (preg_match('/^->/', $path)) {
87: $path = substr($path, 2);
88: }
89:
90: return explode('->', $path);
91: }
92:
93: /**
94: * Returns a property as an array.
95: *
96: * @param $subject
97: * @return array
98: */
99: protected function getPropertyCollection($subject)
100: {
101: if (is_object($subject)) {
102: return get_object_vars($subject);
103: }
104:
105: return $subject;
106: }
107:
108: /**
109: * Return a key that can be used on the current subject.
110: *
111: * @param $key
112: * @param $matches
113: * @return mixed
114: */
115: protected function normalizeKey($key)
116: {
117: if (preg_match(static::$arrayKey, $key, $matches)) {
118: $key = $matches[1];
119: return $key;
120: }
121: return $key;
122: }
123: }
124: