1: <?php
2:
3: namespace Alchemy\orm;
4: use Alchemy\core\Element;
5: use Alchemy\core\schema\Column;
6: use Alchemy\core\schema\ForeignKey;
7: use Alchemy\core\schema\Table;
8: use Alchemy\util\promise\Promise;
9: use Exception;
10:
11:
12: 13: 14: 15: 16:
17: class Relationship extends Element {
18: protected $name;
19: protected $origin;
20: protected $destination;
21: protected $foreignKey;
22: protected $args;
23: protected $inverse;
24:
25:
26: 27: 28: 29: 30: 31: 32: 33:
34: public function __construct($type, $args, $origin, $name, $createBackref = true) {
35: parent::__construct($type);
36:
37: $this->name = $name;
38: $this->origin = $origin;
39:
40: $def = static::get_definition($this->type);
41: $this->args = self::normalize_arg($args, $def['defaults']) +
42: array('backref' => "AutoBackref_{$origin->getName()}_{$name}");
43:
44: if ($dest = $this->args[0]) {
45: $this->destination = is_object($dest) ? $dest
46: : (class_exists($dest) ? $dest::schema() : Table::find($dest));
47: } else {
48: throw new Exception("Must provide Relationship Destination");
49: }
50:
51:
52: if ($this->args['backref'] && $createBackref) {
53: $this->createBackref();
54: }
55: }
56:
57:
58: public function assertDestinationType($dest) {
59: $type = $this->getDestinationClass();
60: if (!($dest instanceof $type)) {
61: throw new Exception(get_class($dest) . " is not {$type}");
62: }
63: }
64:
65:
66: 67: 68: 69: 70:
71: protected function createBackref() {
72: $args = array(
73: $this->origin,
74: 'backref' => $this->name,
75: 'key' => $this->getForeignKey()
76: );
77:
78: $type = $this->getTag('rel.inverse');
79: $this->inverse = Relationship::$type($args, $this->destination, $this->getBackref(), false);
80: $this->destination->addRelationship($this->getBackref(), $this->inverse);
81: }
82:
83:
84: 85: 86: 87: 88:
89: protected function findForeignKey() {
90:
91: if ($key = $this->args['key']) {
92: return is_string($key)
93: ? Column::find($key, $this->origin)->getForeignKey()
94: : $key;
95: }
96:
97:
98: $index = $this->findForeignKeyImpl($this->origin, $this->destination)
99: ?: $this->findForeignKeyImpl($this->destination, $this->origin);
100:
101: if ($index) {
102: return $index;
103: }
104:
105: throw new Exception('ForeignKey could not be found');
106: }
107:
108:
109: 110: 111: 112: 113: 114: 115:
116: protected function findForeignKeyImpl($tableA, $tableB) {
117: $fk = null;
118:
119: foreach ($tableA->listIndexes() as $index) {
120: if ($index instanceof ForeignKey) {
121: if ($index->getSourceTable()->getName() == $tableB->getName()) {
122: if ($fk) {
123: throw new Exception("ForeignKey selection is ambiguous for table[{$tableB->getName()}]");
124: }
125: $fk = $index;
126: }
127: }
128: }
129:
130: return $fk;
131: }
132:
133:
134: 135: 136: 137: 138:
139: public function getBackref() {
140: return $this->args['backref'];
141: }
142:
143:
144: 145: 146: 147: 148:
149: public function getInverse() {
150: return $this->inverse;
151: }
152:
153:
154:
155: 156: 157: 158: 159:
160: public function getDestinationClass() {
161: return $this->destination->getClass();
162: }
163:
164:
165: 166: 167: 168: 169:
170: public function getName() {
171: return $this->name;
172: }
173:
174:
175: public function getRef($origin) {
176: $dest = $this->destination;
177: $expr = new Promise(null, "Alchemy\core\query\Predicate");
178:
179: $ref = new ORMTableRef($dest, $expr);
180: $expr->resolve($ref->equal($this->getRemoteColumnMap($origin)));
181:
182: return $ref;
183: }
184:
185:
186: 187: 188: 189: 190:
191: public function getForeignKey() {
192: if (!$this->foreignKey) {
193: $this->foreignKey = $this->findForeignKey();
194: }
195: return $this->foreignKey;
196: }
197:
198:
199: 200: 201: 202: 203:
204: public function getOriginClass() {
205: return $this->origin->getClass();
206: }
207:
208:
209: 210: 211: 212: 213: 214:
215: public function isSingleObject() {
216: return !!$this->getTag('rel.single');
217: }
218:
219:
220: 221: 222: 223: 224: 225: 226:
227: public function isParent() {
228: return !!$this->getTag('rel.parent');
229: }
230:
231:
232: 233: 234: 235: 236:
237: public function isNullable() {
238: return true;
239: }
240:
241:
242: 243: 244: 245: 246: 247: 248: 249: 250:
251: public function getRemoteColumnMap($origin) {
252: return array();
253: }
254: }
255: