Overview

Namespaces

  • Alchemy
    • core
      • query
      • schema
    • dialect
    • engine
    • orm
    • tests
    • util
      • promise
  • PHP

Classes

  • DataMapper
  • DDL
  • ManyToOne
  • OneToMany
  • OneToOne
  • ORMQuery
  • ORMTable
  • ORMTableRef
  • RelatedSet
  • Relationship
  • Session
  • SessionSelect
  • WorkQueue
  • Overview
  • Namespace
  • Class
  • Tree
  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:  * Base class for defining the relationships between models
 14:  * Subclass this to create separate relationship types like
 15:  * OneToOne, OneToMany, etc.
 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:      * Object constructor for an abstract model relationship
 28:      *
 29:      * @param string $name Relationship Name
 30:      * @param string $origin Originating Class
 31:      * @param array $args array([0] => "DestinationClass", [backref] => "BackrefName")
 32:      * @param bool $createBackref Internal Use Only
 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:         // Assemble the inverse side of this relationship
 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:      * Create an inverse relationship to match this one.
 68:      *
 69:      * @param string $name
 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:      * Find the foreign key that defines how to traverse this relationship
 86:      *
 87:      * @return ForeignKey
 88:      */
 89:     protected function findForeignKey() {
 90:         // User might have specified the key in the definition
 91:         if ($key = $this->args['key']) {
 92:             return is_string($key)
 93:                 ? Column::find($key, $this->origin)->getForeignKey()
 94:                 : $key;
 95:         }
 96: 
 97:         // Try and infer the foreign key
 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:      * Implementation function used by {@see Relationship::findForeignKey()}
111:      *
112:      * @param Table $tableA
113:      * @param Table $tableB
114:      * @return ForeignKey
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:      * Get the name of the backref of this relationship
136:      *
137:      * @return string
138:      */
139:     public function getBackref() {
140:         return $this->args['backref'];
141:     }
142: 
143: 
144:     /**
145:      * Get the inverse side of this relationship
146:      *
147:      * @return string
148:      */
149:     public function getInverse() {
150:         return $this->inverse;
151:     }
152: 
153: 
154: 
155:     /**
156:      * Get the destination class name
157:      *
158:      * @return string
159:      */
160:     public function getDestinationClass() {
161:         return $this->destination->getClass();
162:     }
163: 
164: 
165:     /**
166:      * Get the name of this relationship
167:      *
168:      * @return string
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:      * Get the ForeignKey associated with this relationship
188:      *
189:      * @return ForeignKey
190:      */
191:     public function getForeignKey() {
192:         if (!$this->foreignKey) {
193:             $this->foreignKey = $this->findForeignKey();
194:         }
195:         return $this->foreignKey;
196:     }
197: 
198: 
199:     /**
200:      * Get the class name of the originating class
201:      *
202:      * @return string
203:      */
204:     public function getOriginClass() {
205:         return $this->origin->getClass();
206:     }
207: 
208: 
209:     /**
210:      * Return true if this relationship can structurally only
211:      * ever return a single object.
212:      *
213:      * @return bool
214:      */
215:     public function isSingleObject() {
216:         return !!$this->getTag('rel.single');
217:     }
218: 
219: 
220:     /**
221:      * Return true if the origin of this relationship is the source of
222:      * foreign key index. False if the source of the foreign key is the
223:      * destination of this relationship
224:      *
225:      * @return bool
226:      */
227:     public function isParent() {
228:         return !!$this->getTag('rel.parent');
229:     }
230: 
231: 
232:     /**
233:      * Return true if this relationship can potentially be NULL (empty).
234:      *
235:      * @return bool
236:      */
237:     public function isNullable() {
238:         return true;
239:     }
240: 
241: 
242:     /**
243:      * Return a map of remote column names and their values according
244:      * to this relationship, relative to $origin, for querying. Applies
245:      * whether $origin is a table reference or a DataMapper. Must be
246:      * implemented by all Relationship types.
247:      *
248:      * @param  mixed $origin
249:      * @return array         [Column => Value, ...]
250:      */
251:     public function getRemoteColumnMap($origin) {
252:         return array();
253:     }
254: }
255: 
API documentation generated by ApiGen 2.8.0