1: <?php
2:
3: namespace PHPixie\ORM\Relationships\Type\ManyToMany;
4:
5: class Handler extends \PHPixie\ORM\Relationships\Relationship\Implementation\Handler
6: implements \PHPixie\ORM\Relationships\Relationship\Handler\Mapping\Database,
7: \PHPixie\ORM\Relationships\Relationship\Handler\Preloading
8: {
9: public function query($side, $related)
10: {
11: $config = $side->config();
12: $side = $side->type();
13: $entity = $config->get($side.'Model');
14: $property = $config->get($side.'Property');
15: $repository = $this->getRepository($entity);
16: return $repository->query()->relatedTo($property, $related);
17: }
18:
19: public function loadProperty($side, $entity)
20: {
21: $config = $side->config();
22: $loader = $this->query($side, $entity)->find();
23: $editable = $this->loaders->editableProxy($loader);
24:
25: $opposing = $this->getOpposing($side->type());
26:
27: $property = $entity->getRelationshipProperty($config->{$opposing.'Property'});
28: $property->setValue($editable);
29: }
30:
31: public function linkPlan($config, $leftItems, $rightItems)
32: {
33: $plan = $this->plans->steps();
34: list($leftSide, $rightSide) = $this->plannerSides($config, $leftItems, $rightItems);
35: $pivot = $this->plannerPivot($config);
36: $this->planners->pivot()->link($pivot, $leftSide, $rightSide, $plan);
37:
38: return $plan;
39: }
40:
41: public function unlinkPlan($config, $leftItems, $rightItems)
42: {
43: $plan = $this->plans->steps();
44: list($leftSide, $rightSide) = $this->plannerSides($config, $leftItems, $rightItems);
45: $pivot = $this->plannerPivot($config);
46: $this->planners->pivot()->unlink($pivot, $leftSide, $rightSide, $plan);
47:
48: return $plan;
49: }
50:
51: public function unlinkAllPlan($side, $items)
52: {
53: $config = $side->config();
54: $plan = $this->plans->steps();
55: $opposing = $this->getOpposing($side->type());
56: $plannerSide = $this->plannerSide($config, $opposing, $items);
57: $pivot = $this->plannerPivot($config);
58: $this->planners->pivot()->unlinkAll($pivot, $plannerSide, $plan);
59:
60: return $plan;
61: }
62:
63: protected function plannerSides($config, $leftItems, $rightItems)
64: {
65: $sides = array();
66: foreach (array('left', 'right') as $side) {
67: $items = $side === 'left' ? $leftItems : $rightItems;
68: $sides[] = $this->plannerSide($config, $side, $items);
69: }
70:
71: return $sides;
72: }
73:
74: protected function plannerSide($config, $side, $items)
75: {
76: $entity = $config->get($side.'Model');
77: return $this->planners->pivot()->side(
78: $items,
79: $this->getRepository($entity),
80: $config->get($side.'PivotKey')
81: );
82: }
83:
84: protected function plannerPivot($config)
85: {
86: $pivotPlanner = $this->planners->pivot();
87:
88: if ($config->pivotConnection !== null) {
89: return $pivotPlanner->pivotByConnectionName($config->pivotConnection, $config->pivot);
90: }
91:
92: $connection = $this->getRepository($config->leftModel)->connection();
93: return $pivotPlanner->pivot($connection, $config->pivot);
94: }
95:
96: public function mapDatabaseQuery($query, $side, $collectionCondition, $plan)
97: {
98: $dependencies = $this->getMappingDependencies($side);
99: $config = $dependencies['config'];
100: $sideRepository = $dependencies['sideRepository'];
101: $inPlanner = $this->planners->in();
102:
103: $sideIdField = $this->getIdField($sideRepository);
104:
105: $sideQuery = $sideRepository->databaseSelectQuery();
106: $this->mappers->conditions()->map(
107: $sideQuery,
108: $sideRepository->modelName(),
109: $collectionCondition->conditions(),
110: $plan
111: );
112:
113:
114: $pivotQuery = $dependencies['pivot']->databaseSelectQuery();
115: $inPlanner->subquery(
116: $pivotQuery,
117: $config->get($dependencies['type'].'PivotKey'),
118: $sideQuery,
119: $sideIdField,
120: $plan
121: );
122:
123: $inPlanner->subquery(
124: $query,
125: $this->getIdField($dependencies['opposingRepository']),
126: $pivotQuery,
127: $config->get($dependencies['opposing'].'PivotKey'),
128: $plan,
129: $collectionCondition->logic(),
130: $collectionCondition->isNegated()
131: );
132: }
133:
134: public function mapPreload($side, $preloadProperty, $result, $plan)
135: {
136:
137: $dependencies = $this->getMappingDependencies($side);
138: $config = $dependencies['config'];
139: $sideRepository = $dependencies['sideRepository'];
140: $inPlanner = $this->planners->in();
141:
142: $pivotQuery = $dependencies['pivot']->databaseSelectQuery();
143:
144: $inPlanner->result(
145: $pivotQuery,
146: $config->get($dependencies['opposing'].'PivotKey'),
147: $result,
148: $this->getIdField($dependencies['opposingRepository']),
149: $plan
150: );
151:
152: $pivotResult = $this->steps->reusableResult($pivotQuery);
153: $plan->add($pivotResult);
154:
155: $sideQuery = $sideRepository->databaseSelectQuery();
156:
157: $inPlanner->result(
158: $sideQuery,
159: $this->getIdField($sideRepository),
160: $pivotResult,
161: $config->get($dependencies['type'].'PivotKey'),
162: $plan
163: );
164:
165: $preloadStep = $this->steps->reusableResult($sideQuery);
166: $plan->add($preloadStep);
167: $loader = $this->loaders->reusableResult($sideRepository, $preloadStep);
168:
169: $preloadingProxy = $this->loaders->preloadingProxy($loader);
170: $cachingProxy = $this->loaders->cachingProxy($preloadingProxy);
171:
172: $this->mappers->preload()->map(
173: $preloadingProxy,
174: $sideRepository->modelName(),
175: $preloadProperty->preload(),
176: $preloadStep,
177: $plan
178: );
179:
180: return $this->relationship->preloader($side, $sideRepository->config(), $preloadStep,
181: $cachingProxy, $pivotResult);
182:
183: }
184:
185: protected function getMappingDependencies($side)
186: {
187: $dependencies = array();
188:
189: $type = $side->type();
190: $config = $side->config();
191: $opposing = $this->getOpposing($type);
192:
193: return array(
194: 'config' => $config,
195: 'type' => $type,
196: 'opposing' => $opposing,
197: 'pivot' => $this->plannerPivot($config),
198: 'sideRepository' => $this->getRepository($config->get($type.'Model')),
199: 'opposingRepository' => $this->getRepository($config->get($opposing.'Model'))
200: );
201:
202: return $dependencies;
203: }
204:
205: protected function getOpposing($type)
206: {
207: return $type === 'left' ? 'right' : 'left';
208: }
209:
210: public function linkProperties($config, $left, $right)
211: {
212: $this->processProperties('add', $left, $config->leftProperty, $right);
213: $this->processProperties('add', $right, $config->rightProperty, $left);
214: }
215:
216: public function unlinkProperties($config, $left, $right)
217: {
218: $this->processProperties('remove', $left, $config->leftProperty, $right);
219: $this->processProperties('remove', $right, $config->rightProperty, $left);
220: }
221:
222: public function unlinkAllProperties($side, $entity)
223: {
224: $property = $this->getPropertyIfLoaded($entity, $side->propertyName());
225: if ($property !== null) {
226: $loader = $property->value();
227: $items = $loader->accessedEntities();
228: $type = $side->type();
229: $itemsProperty = $side->config()->get($type.'Property');
230: $this->processProperties('remove', $items, $itemsProperty, $entity);
231: $loader->removeAll();
232: }
233: }
234:
235: public function resetProperties($side, $items)
236: {
237: $opposing = $this->getOpposing($side->type());
238: $itemsProperty = $side->config()->get($opposing.'Property');
239:
240: if(!is_array($items))
241: $items = array($items);
242:
243: foreach($items as $item) {
244: if(!$this->isEntityValue($item))
245: continue;
246: $property = $this->getPropertyIfLoaded($item, $itemsProperty);
247: if($property !== null)
248: $property->reset();
249: }
250: }
251:
252: protected function processProperties($action, $owners, $ownerProperty, $items)
253: {
254: if (!is_array($owners))
255: $owners = array($owners);
256:
257: if (!is_array($items))
258: $items = array($items);
259:
260: $resetOwners = false;
261: foreach ($items as $item) {
262: if (!$this->isEntityValue($item)) {
263: $resetOwners = true;
264: break;
265: }
266: }
267:
268: foreach ($owners as $owner) {
269: if (!$this->isEntityValue($owner))
270: continue;
271:
272: $property = $this->getPropertyIfLoaded($owner, $ownerProperty);
273: if($property === null)
274: continue;
275:
276: if ($resetOwners) {
277: $property->reset();
278: continue;
279: }
280:
281: $loader = $property->value();
282: if ($action === 'remove') {
283: $loader->remove($items);
284: } else {
285: $loader->add($items);
286: }
287: }
288: }
289:
290: public function handleDelete($side, $result, $plan, $sidePath)
291: {
292: $config = $side->config();
293: $opposing = $this->getOpposing($side->type());
294:
295: $pivot = $this->plannerPivot($config);
296: $query = $pivot->databaseDeleteQuery();
297: $pivotKey = $config->get($opposing.'PivotKey');
298:
299: $repository = $this->getRepository($side->modelName());
300: $idField = $this->getIdField($repository);
301: $this->planners->in()->result($query, $pivotKey, $result, $idField, $plan);
302: $deleteStep = $this->steps->query($query);
303: $plan->add($deleteStep);
304: }
305:
306: protected function getRepository($modelName)
307: {
308: return $this->models->database()->repository($modelName);
309: }
310:
311: protected function getIdField($repository)
312: {
313: return $repository->config()->idField;
314: }
315:
316: }
317: