1: <?php
2:
3: namespace PHPixie\ORM\Loaders\Loader\Proxy;
4:
5: class Editable extends \PHPixie\ORM\Loaders\Loader\Proxy
6: {
7: protected $maxAccessedOffset = -1;
8: protected $idOffsets = array();
9:
10: protected $skippedIds = array();
11: protected $deletedOffsets = array();
12:
13: protected $skippedOffsets = array();
14: protected $existingBefore = array();
15:
16: protected $loaderItemsCount = null;
17:
18: protected $addedEntities = array();
19: protected $addedIdsOffsets = array();
20: protected $addedOffsetsIds = array();
21:
22: public function add($entities)
23: {
24: $offset = count($this->addedEntities);
25: foreach ($entities as $entity) {
26: $id = $entity->id();
27: if(array_key_exists($id, $this->addedIdsOffsets)) {
28: $offset = $this->addedIdsOffsets[$id];
29: $this->addedEntities[$offset]=$entity;
30: }else{
31: $this->skipId($id);
32: $this->addedEntities[$offset]=$entity;
33:
34: $this->addedIdsOffsets[$id]=$offset;
35: $this->addedOffsetsIds[$offset]=$id;
36: }
37: }
38: }
39:
40: public function remove($entities)
41: {
42: foreach ($entities as $entity) {
43: $id = $entity->id();
44: $this->skipId($id);
45: if(array_key_exists($id, $this->addedIdsOffsets)) {
46: $offset = $this->addedIdsOffsets[$id];
47: if($offset <= $this->maxAccessedOffset) {
48: $this->maxAccessedOffset--;
49: }
50: $this->removeAddedById($id);
51: }
52: }
53: $this->updateSkippedOffsets();
54: }
55:
56: public function accessedEntities()
57: {
58: $entities = array();
59: for($i=0; $i<=$this->maxAccessedOffset; $i++)
60: $entities[]=$this->getByOffset($i);
61: return $entities;
62: }
63:
64: public function removeAll()
65: {
66: $this->loaderItemsCount = 0;
67: $this->maxAccessedOffset = -1;
68: $this->idOffsets = array();
69: $this->skippedIds = array();
70: $this->deletedOffsets = array();
71:
72: $this->skippedOffsets = array();
73: $this->existingBefore = array();
74:
75: $this->addedEntities = array();
76: $this->addedIdsOffsets = array();
77: $this->addedOffsetsIds = array();
78:
79: $this->loader = null;
80: }
81:
82: protected function removeAddedById($id)
83: {
84: $offset = $this->addedIdsOffsets[$id];
85: array_splice($this->addedOffsetsIds, $offset, 1);
86: array_splice($this->addedEntities, $offset, 1);
87: $this->addedIdsOffsets = array_flip($this->addedOffsetsIds);
88: }
89:
90: public function offsetExists($offset)
91: {
92: return $this->getEntityByOffset($offset) !== null;
93: }
94:
95: public function getByOffset($offset)
96: {
97: $entity = $this->getEntityByOffset($offset);
98: if($entity === null)
99: throw new \PHPixie\ORM\Exception\Loader("Offset $offset does not exist.");
100:
101: return $entity;
102: }
103:
104: protected function getEntityByOffset($offset)
105: {
106: $this->assertAllowedOffset($offset);
107: $loaderOffset = $this->loaderOffset($offset);
108:
109: if ($this->loaderItemsCount !== null && $loaderOffset >= $this->loaderItemsCount) {
110: $entity = $this->getAddedByOffset($loaderOffset - $this->loaderItemsCount);
111: }else{
112: $entity = $this->getLoadedByOffset($loaderOffset, $offset);
113: }
114:
115: if ($entity !== null && $offset === $this->maxAccessedOffset+1)
116: $this->maxAccessedOffset++;
117:
118: return $entity;
119: }
120:
121: protected function assertAllowedOffset($offset)
122: {
123: if ($offset > $this->maxAccessedOffset+1)
124: throw new \PHPixie\ORM\Exception\Loader("Items can only be accessed in sequential order");
125: }
126:
127: protected function getAddedByOffset($addedOffset)
128: {
129: if(!array_key_exists($addedOffset, $this->addedEntities))
130: return null;
131:
132: $entity = $this->addedEntities[$addedOffset];
133:
134: if ($entity->isDeleted()) {
135: $this->removeAddedById($this->addedOffsetsIds[$addedOffset]);
136: return $this->getAddedByOffset($addedOffset);
137: }
138:
139: return $entity;
140: }
141:
142: protected function getLoadedByOffset($loaderOffset, $offset)
143: {
144: if(!$this->loader->offsetExists($loaderOffset)){
145:
146: if ($this->loaderItemsCount !== null)
147: return null;
148:
149: $this->loaderItemsCount = $loaderOffset;
150: return $this->getAddedByOffset(0);
151: }
152:
153: $entity = $this->loader->getByOffset($loaderOffset);
154: if ($entity->isDeleted()) {
155: $this->deletedOffsets[] = $loaderOffset;
156: return $this->updateAndGet($offset);
157: }
158:
159: $id = $entity->id();
160: $this->idOffsets[$id] = $loaderOffset;
161:
162: if (array_key_exists($id, $this->skippedIds)) {
163: $this->skippedIds[$id] = $loaderOffset;
164: return $this->updateAndGet($offset);
165: }
166:
167: return $entity;
168: }
169:
170: protected function updateAndGet($offset)
171: {
172: $this->updateSkippedOffsets();
173: return $this->getByOffset($offset);
174: }
175:
176: protected function skipId($id)
177: {
178: if (!array_key_exists($id, $this->skippedIds)) {
179: $offset = null;
180: if (array_key_exists($id, $this->idOffsets)) {
181: $offset = $this->idOffsets[$id];
182: }
183: $this->skippedIds[$id] = $offset;
184: }
185: }
186:
187: protected function updateSkippedOffsets()
188: {
189: $skippedOffsets = array();
190: foreach($this->skippedIds as $skippedOffset)
191: if($skippedOffset!==null)
192: $skippedOffsets[]=$skippedOffset;
193:
194: foreach($this->deletedOffsets as $deletedOffset)
195: $skippedOffsets[]=$deletedOffset;
196: $skippedOffsets = array_unique($skippedOffsets);
197: sort($skippedOffsets);
198: $count = count($skippedOffsets);
199:
200: $this->existingBefore = array();
201: foreach($skippedOffsets as $key => $skippedOffset) {
202: if($key === 0) {
203: $this->existingBefore[] = $skippedOffset;
204: }else{
205: $this->existingBefore[] = $this->existingBefore[$key-1] + $skippedOffset - $skippedOffsets[$key-1] - 1;
206: }
207: }
208:
209: $this->skippedOffsets = $skippedOffsets;
210: }
211:
212: protected function loaderOffset($offset)
213: {
214: if (empty($this->skippedOffsets))
215: return $offset;
216:
217: return $this->getAdjustment($offset);
218: }
219:
220: protected function getAdjustment($offset)
221: {
222: $low = 0;
223: $count = count($this->existingBefore);
224: $high = $count - 1;
225: $result = -1;
226:
227: while ($low <= $high) {
228: $mid = (int) (($high - $low) / 2) + $low;
229:
230: $midOffset = $this->existingBefore[$mid];
231:
232: if ($midOffset <= $offset) {
233: $low = $mid + 1;
234: } elseif ($midOffset > $offset) {
235: $high = $mid - 1;
236: }
237: }
238:
239: if($low === 0) {
240: $adjusted = $offset;
241: }else{
242: $adjusted = $this->skippedOffsets[$low-1] + $offset - $this->existingBefore[$low-1] + 1;
243: }
244:
245: return $adjusted;
246:
247: }
248:
249: }