Completed
Push — master ( ccbc8f...b66c0a )
by Peter
06:36
created

CompositionIterator::getCurrentField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: peter
5
 * Date: 07.03.18
6
 * Time: 17:46
7
 */
8
9
namespace Maslosoft\Mangan\Helpers;
10
11
12
use Countable;
13
use Iterator;
14
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
15
use Maslosoft\Addendum\Utilities\ClassChecker;
16
use Maslosoft\Mangan\Mangan;
17
use Maslosoft\Mangan\Meta\ManganMeta;
18
19
/**
20
 * Iterate over composition of documents.
21
 *
22
 * NOTE: This will include only AnnotatedInterface instances.
23
 *
24
 * @package Maslosoft\Mangan\Helpers
25
 */
26
class CompositionIterator implements Iterator, Countable
27
{
28
	private $model = null;
29
30
	private $direct = false;
31
32
	private $types = [];
33
34
	/**
35
	 * Models holder
36
	 * @var null|AnnotatedInterface[]
37
	 */
38
	private $models = null;
39
40
	private $pointer = 0;
41
42
	private $fieldNames = [];
43
44 60
	public function __construct(AnnotatedInterface $model)
45
	{
46 60
		$this->model = $model;
47 60
	}
48
49
	/**
50
	 * Limit results to only direct descendants.
51
	 * @return $this
52
	 */
53 6
	public function direct()
54
	{
55 6
		$this->direct = true;
56 6
		return $this;
57
	}
58
59
	/**
60
	 * Limit results to only to the type provided.
61
	 *
62
	 * The `$type` should be class or interface name
63
	 * or object instance.
64
	 *
65
	 * Repeated calls will add types uniquely.
66
	 *
67
	 * @param $type string|object
68
	 * @param $include boolean Whether to include this type or skip
69
	 * @return $this
70
	 */
71 58
	public function ofType($type, $include = true)
72
	{
73 58
		if (is_object($type))
74
		{
75
			$type = get_class($type);
76
		}
77 58
		assert(is_string($type));
78 58
		assert(ClassChecker::exists($type));
79 58
		$this->types[$type] = $include;
80 58
		return $this;
81
	}
82
83
	/**
84
	 * Get currently iterated over field name,
85
	 * which have models
86
	 * @return string
87
	 */
88
	public function getCurrentField()
89
	{
90
		if(isset($this->fieldNames[$this->pointer]))
91
		{
92
			return $this->fieldNames[$this->pointer];
93
		}
94
		return '';
95
	}
96
97 60
	private function init()
98
	{
99 60
		if (null === $this->models)
100
		{
101 60
			$this->models = [];
102 60
			$this->iterate($this->model);
103
		}
104 60
	}
105
106 60
	private function iterate($model)
107
	{
108 60
		foreach (ManganMeta::create($model)->fields() as $name => $meta)
109
		{
110
			// Not one of:
111
			// * Embedded(Array)
112
			// * DbRef(Array)
113
			// * Related(Array)
114 60
			if(!$meta->owned)
115
			{
116 60
				continue;
117
			}
118 18
			if (is_array($model->$name))
119
			{
120 11
				foreach ($model->$name as $child)
121
				{
122 11
					if ($this->skip($child))
123
					{
124
						continue;
125
					}
126 11
					if ($this->doInclude($child))
127
					{
128 7
						$this->fieldNames[] = $name;
129 7
						$this->models[] = $child;
130
					}
131 11
					if($this->recurse())
132
					{
133 11
						$this->iterate($child);
134
					}
135
				}
136 11
				continue;
137
			}
138 15
			if ($this->skip($model->$name))
139
			{
140
				continue;
141
			}
142 15
			if ($this->doInclude($model->$name))
143
			{
144 12
				$this->fieldNames[] = $name;
145 12
				$this->models[] = $model->$name;
146
			}
147 15
			if($this->recurse())
148
			{
149 15
				$this->iterate($model->$name);
150
			}
151
		}
152 60
	}
153
154 18
	private function skip($model)
155
	{
156
		// Non-object
157 18
		if (!is_object($model))
158
		{
159
			return true;
160
		}
161
		// Skip if not annotated
162 18
		if (!$model instanceof AnnotatedInterface)
163
		{
164
			return true;
165
		}
166 18
		return false;
167
	}
168
169
	/**
170
	 * Whether to include `$model` in result
171
	 * @param $model
172
	 * @return bool
173
	 */
174 18
	private function doInclude($model)
175
	{
176
		// Don't skip if no types
177 18
		if (empty($this->types))
178
		{
179 6
			return true;
180
		}
181
182
		// Include if is_a type
183 16
		foreach ($this->types as $type => $include)
184
		{
185 16
			if (is_a($model, $type))
186
			{
187 16
				return $include;
188
			}
189
		}
190 5
		return false;
191
	}
192
193 18
	private function recurse()
194
	{
195 18
		return !$this->direct;
196
	}
197
198 14
	public function current()
199
	{
200 14
		$this->init();
201 14
		return $this->models[$this->pointer];
202
	}
203
204 14
	public function next()
205
	{
206 14
		$this->init();
207 14
		++$this->pointer;
208 14
	}
209
210
	public function key()
211
	{
212
		$this->init();
213
		return $this->pointer;
214
	}
215
216 60
	public function valid()
217
	{
218 60
		$this->init();
219 60
		return isset($this->models[$this->pointer]);
220
	}
221
222 60
	public function rewind()
223
	{
224 60
		$this->init();
225 60
		$this->pointer = 0;
226 60
	}
227
228 5
	public function count()
229
	{
230 5
		$this->init();
231 5
		return count($this->models);
232
	}
233
234
235
}