1: <?php
2:
3: /**
4: * The Relation complex data type
5: *
6: * @package alpha::model::types
7: * @since 1.0
8: * @author John Collins <dev@alphaframework.org>
9: * @version $Id: Relation.inc 1607 2012-12-17 11:26:17Z alphadevx $
10: * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
11: * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
12: * All rights reserved.
13: *
14: * <pre>
15: * Redistribution and use in source and binary forms, with or
16: * without modification, are permitted provided that the
17: * following conditions are met:
18: *
19: * * Redistributions of source code must retain the above
20: * copyright notice, this list of conditions and the
21: * following disclaimer.
22: * * Redistributions in binary form must reproduce the above
23: * copyright notice, this list of conditions and the
24: * following disclaimer in the documentation and/or other
25: * materials provided with the distribution.
26: * * Neither the name of the Alpha Framework nor the names
27: * of its contributors may be used to endorse or promote
28: * products derived from this software without specific
29: * prior written permission.
30: *
31: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
34: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
35: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
36: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
41: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
42: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44: * </pre>
45: *
46: */
47: class Relation extends AlphaType implements AlphaTypeInterface {
48: /**
49: * The name of the business object class which this class is related to
50: *
51: * @var string
52: * @since 1.0
53: */
54: private $relatedClass;
55:
56: /**
57: * The name of the fields of the business object class by which this class is related by
58: *
59: * @var string
60: * @since 1.0
61: */
62: private $relatedClassField;
63:
64: /**
65: * The name of the field from the related business object class which is displayed by the selection widget
66: *
67: * @var string
68: * @since 1.0
69: */
70: private $relatedClassDisplayField;
71:
72: /**
73: * An array of fields to use the values of while rendering related display values via the selection widget
74: *
75: * @var array
76: * @since 1.0
77: */
78: private $relatedClassHeaderFields = array();
79:
80: /**
81: * The name of the business object class on the left of a MANY-TO-MANY relation
82: *
83: * @var string
84: * @since 1.0
85: */
86: private $relatedClassLeft;
87:
88: /**
89: * The name of the field from the related business object class on the left of a
90: * MANY-TO-MANY relation which is displayed by the selection widget
91: *
92: * @var string
93: * @since 1.0
94: */
95: private $relatedClassLeftDisplayField;
96:
97: /**
98: * The name of the business object class on the right of a MANY-TO-MANY relation
99: *
100: * @var string
101: * @since 1.0
102: */
103: private $relatedClassRight;
104:
105: /**
106: * The name of the field from the related business object class on the right of a
107: * MANY-TO-MANY relation which is displayed by the selection widget
108: *
109: * @var string
110: * @since 1.0
111: */
112: private $relatedClassRightDisplayField;
113:
114: /**
115: * The type of relation ('MANY-TO-ONE' or 'ONE-TO-MANY' or 'ONE-TO-ONE' or 'MANY-TO-MANY')
116: *
117: * @var string
118: * @since 1.0
119: */
120: private $relationType;
121:
122: /**
123: * In the case of MANY-TO-MANY relationship, a lookup object will be required
124: *
125: * @var RelationLookup
126: * @since 1.0
127: */
128: private $lookup;
129:
130: /**
131: * When building a relation with the TagObject BO, set this to the name of the tagged class
132: *
133: * @var string
134: * @since 1.0
135: */
136: private $taggedClass;
137:
138: /**
139: * An array of the allowable relationship types ('MANY-TO-ONE' or 'ONE-TO-MANY' or 'ONE-TO-ONE' or 'MANY-TO-MANY')
140: *
141: * @var array
142: * @since 1.0
143: */
144: private $allowableRelationTypes = array('MANY-TO-ONE','ONE-TO-MANY','ONE-TO-ONE','MANY-TO-MANY');
145:
146: /**
147: * The object ID (OID) value of the related object. In the special case of a MANY-TO-MANY
148: * relation, contains the OID of the object on the current, accessing side. Can contain NULL.
149: *
150: * @var mixed
151: * @since 1.0
152: */
153: private $value = NULL;
154:
155: /**
156: * The validation rule for the Relation type
157: *
158: * @var string
159: * @since 1.0
160: */
161: private $validationRule;
162:
163: /**
164: * The error message for the Relation type when validation fails
165: *
166: * @var string
167: * @since 1.0
168: */
169: protected $helper;
170:
171: /**
172: * The size of the value for the this Relation
173: *
174: * @var integer
175: * @since 1.0
176: */
177: private $size = 11;
178:
179: /**
180: * The absolute maximum size of the value for the this Relation
181: *
182: * @var integer
183: * @since 1.0
184: */
185: const MAX_SIZE = 11;
186:
187: /**
188: * Constructor
189: *
190: * @since 1.0
191: */
192: public function __construct() {
193: $this->validationRule = AlphaValidator::REQUIRED_INTEGER;
194: $this->helper = ' not a valid Relation value! A maximum of '.$this->size.' characters is allowed.';
195: }
196:
197: /**
198: * Set the name of the business object class that this class is related to
199: *
200: * @param string $RC
201: * @param string $side Only required for MANY-TO-MANY relations
202: * @since 1.0
203: * @throws IllegalArguementException
204: */
205: public function setRelatedClass($RC, $side='') {
206: if(in_array($RC, AlphaDAO::getBOClassNames())) {
207: // load the class definition if it has not been loaded already
208: AlphaDAO::loadClassDef($RC);
209: switch($side) {
210: case '':
211: $this->relatedClass = $RC;
212: break;
213: case 'left':
214: $this->relatedClassLeft = $RC;
215: break;
216: case 'right':
217: $this->relatedClassRight = $RC;
218: break;
219: default:
220: throw new IllegalArguementException('The side paramter ['.$RC.'] is not valid!');
221: }
222: }else{
223: throw new IllegalArguementException('The class ['.$RC.'] is not defined anywhere!');
224: }
225: }
226:
227: /**
228: * Get the name of the business object class that this class is related to
229: *
230: * @param string $RC
231: * @return string
232: * @since 1.0
233: * @throws IllegalArguementException
234: */
235: public function getRelatedClass($side='') {
236: switch($side) {
237: case '':
238: return $this->relatedClass;
239: break;
240: case 'left':
241: return $this->relatedClassLeft;
242: break;
243: case 'right':
244: return $this->relatedClassRight;
245: break;
246: default:
247: throw new IllegalArguementException('The side paramter ['.$RC.'] is not valid!');
248: return '';
249: }
250: }
251:
252: /**
253: * Setter for the field of the related class
254: *
255: * @param string $RCF
256: * @since 1.0
257: * @throws IllegalArguementException
258: * @throws AlphaException
259: */
260: public function setRelatedClassField($RCF) {
261: try{
262: // use reflection to sure the related class has the field $RCF
263: $reflection = new ReflectionClass($this->relatedClass);
264: $properties = $reflection->getProperties();
265: $fieldFound = false;
266:
267: foreach($properties as $propObj) {
268: if($RCF == $propObj->name) {
269: $fieldFound = true;
270: break;
271: }
272: }
273:
274: if($fieldFound)
275: $this->relatedClassField = $RCF;
276: else
277: throw new IllegalArguementException('The field ['.$RCF.'] was not found in the class ['.$this->relatedClass.']');
278: }catch (Exception $e) {
279: throw new AlphaException($e->getMessage());
280: }
281: }
282:
283: /**
284: * Getter for the field of the related class
285: *
286: * @return string
287: * @since 1.0
288: */
289: public function getRelatedClassField() {
290: return $this->relatedClassField;
291: }
292:
293: /**
294: * Setter for ONE-TO-MANY relations, which sets the header fields to
295: * render from the related class
296: *
297: * @param array $fieldNames
298: * @since 1.0
299: */
300: public function setRelatedClassHeaderFields($fieldNames) {
301: $this->relatedClassHeaderFields = $fieldNames;
302: }
303:
304: /**
305: * Getter for the selection widget field headings of the related class
306: *
307: * @return array
308: * @since 1.0
309: */
310: public function getRelatedClassHeaderFields() {
311: return $this->relatedClassHeaderFields;
312: }
313:
314: /**
315: * Setter for the display field from the related class
316: *
317: * @param string $RCDF
318: * @param string $side Only required for MANY-TO-MANY relations
319: * @since 1.0
320: * @throws IllegalArguementException
321: */
322: public function setRelatedClassDisplayField($RCDF, $side='') {
323: switch($side) {
324: case '':
325: $this->relatedClassDisplayField = $RCDF;
326: break;
327: case 'left':
328: $this->relatedClassLeftDisplayField = $RCDF;
329: break;
330: case 'right':
331: $this->relatedClassRightDisplayField = $RCDF;
332: break;
333: default:
334: throw new IllegalArguementException('The side paramter ['.$RC.'] is not valid!');
335: }
336: }
337:
338: /**
339: * Getter for the display field from the related class
340: *
341: * @param string $side Only required for MANY-TO-MANY relations
342: * @return string
343: * @since 1.0
344: * @throws IllegalArguementException
345: */
346: public function getRelatedClassDisplayField($side='') {
347: switch($side) {
348: case '':
349: return $this->relatedClassDisplayField;
350: break;
351: case 'left':
352: return $this->relatedClassLeftDisplayField;
353: break;
354: case 'right':
355: return $this->relatedClassRightDisplayField;
356: break;
357: default:
358: throw new IllegalArguementException('The side paramter ['.$RC.'] is not valid!');
359: return '';
360: }
361: }
362:
363: /**
364: * Setter for the relation type
365: *
366: * @param string $RT
367: * @throws IllegalArguementException
368: * @throws FailedLookupCreateException
369: * @throws IllegalArguementException
370: * @since 1.0
371: */
372: public function setRelationType($RT) {
373: if(in_array($RT, $this->allowableRelationTypes)) {
374: $this->relationType = $RT;
375: if($RT == 'MANY-TO-MANY') {
376: try {
377: $this->lookup = new RelationLookup($this->relatedClassLeft, $this->relatedClassRight);
378: }catch (FailedLookupCreateException $flce) {
379: throw $flce;
380: }catch (IllegalArguementException $iae) {
381: throw $iae;
382: }
383: }
384: }else{
385: throw new IllegalArguementException('Relation type of ['.$RT.'] is invalid!');
386: }
387: }
388:
389: /**
390: * Getter for the relation type
391: *
392: * @return string
393: * @since 1.0
394: */
395: public function getRelationType() {
396: return $this->relationType;
397: }
398:
399: /**
400: * Setter for the value (OID of related object) of this relation
401: *
402: * @param integer $val
403: * @since 1.0
404: * @throws IllegalArguementException
405: */
406: public function setValue($val) {
407: if(empty($val)) {
408: $this->value = NULL;
409: }else{
410: if(!AlphaValidator::isInteger($val))
411: throw new IllegalArguementException("[$val]".$this->helper);
412:
413: if (strlen($val) <= $this->size) {
414: $this->value = str_pad($val, 11, '0', STR_PAD_LEFT);
415: }else{
416: throw new IllegalArguementException("[$val]".$this->helper);
417: }
418: }
419: }
420:
421: /**
422: * Getter for the Relation value
423: *
424: * @return mixed
425: * @since 1.0
426: */
427: public function getValue() {
428: return $this->value;
429: }
430:
431: /**
432: * Get the validation rule
433: *
434: * @return string
435: * @since 1.0
436: */
437: public function getRule() {
438: return $this->validationRule;
439: }
440:
441: /**
442: * Setter to override the default validation rule
443: *
444: * @param string $rule
445: * @since 1.0
446: */
447: public function setRule($rule) {
448: $this->validationRule = $rule;
449: }
450:
451: /**
452: * Getter for the display value of the related class field. In the case of a
453: * MANY-TO-MANY Relation, a comma-seperated sorted list of values is returned.
454: *
455: * @param string $accessingClassName Used to indicate the reading side when accessing from MANY-TO-MANY relation (leave blank for other relation types)
456: * @return string
457: * @since 1.0
458: * @throws IllegalArguementException
459: * @throws AlphaException
460: */
461: public function getRelatedClassDisplayFieldValue($accessingClassName='') {
462: global $config;
463:
464: if($this->relationType == 'MANY-TO-MANY') {
465: /*
466: * 1. Use RelationLookup to get OIDs of related objects
467: * 2. Load related objects
468: * 3. Access the value of the field on the object to build the
469: * comma-seperated list.
470: */
471: if(empty($this->lookup))
472: throw new AlphaException('Tried to load related MANY-TO-MANY fields but no RelationLookup set on the Relation object!');
473:
474: if(empty($accessingClassName))
475: throw new IllegalArguementException('Tried to load related MANY-TO-MANY fields but no accessingClassName parameter set on the call to getRelatedClassDisplayFieldValue!');
476:
477: // load objects on the right from accessing on the left
478: if($accessingClassName == $this->relatedClassLeft) {
479: AlphaDAO::loadClassDef($this->relatedClassRight);
480: $obj = new $this->relatedClassRight;
481:
482: $lookupObjects = $this->lookup->loadAllByAttribute('leftID', $this->value);
483:
484: $values = array();
485: foreach($lookupObjects as $lookupObject) {
486: $obj->load($lookupObject->get('rightID'));
487: array_push($values, $obj->get($this->relatedClassRightDisplayField));
488: }
489: // sort array, then return as comma-seperated string
490: asort($values);
491: return implode(',', $values);
492: }
493: // load objects on the left from accessing on the right
494: if($accessingClassName == $this->relatedClassRight) {
495: AlphaDAO::loadClassDef($this->relatedClassLeft);
496: $obj = new $this->relatedClassLeft;
497:
498: $lookupObjects = $this->lookup->loadAllByAttribute('rightID', $this->value);
499:
500: $values = array();
501: foreach($lookupObjects as $lookupObject) {
502: $obj->load($lookupObject->get('leftID'));
503: array_push($values, $obj->get($this->relatedClassLeftDisplayField));
504: }
505: // sort array, then return as comma-seperated string
506: asort($values);
507: return implode(',', $values);
508: }
509: }else{
510: AlphaDAO::loadClassDef($this->relatedClass);
511: $obj = new $this->relatedClass;
512: // making sure we have an object to load
513: if(empty($this->value) || $this->value == '00000000000') {
514: return '';
515: }else{
516: $obj->load($this->value);
517: return $obj->get($this->relatedClassDisplayField);
518: }
519: }
520: }
521:
522: /**
523: * For one-to-many and many-to-many relations, get the objects on the other side
524: *
525: * string $accessingClassName Used to indicate the reading side when accessing from MANY-TO-MANY relation (leave blank for other relation types)
526: * @return array
527: * @since 1.0
528: * @throws IllegalArguementException
529: * @throws AlphaException
530: */
531: public function getRelatedObjects($accessingClassName='') {
532: global $config;
533:
534: if($this->relationType == 'ONE-TO-MANY') {
535:
536: if($this->getValue() == '') // if the value is empty, then return an empty array
537: return array();
538:
539: try {
540: AlphaDAO::loadClassDef($this->relatedClass);
541: }catch(IllegalArguementException $e) {
542: throw new AlphaException('Could not load the definition for the BO class ['.$this->relatedClass.']');
543: }
544:
545: $obj = new $this->relatedClass;
546: if($this->relatedClass == 'TagObject') {
547: $objects = $obj->loadTags($this->taggedClass, $this->getValue());
548: }else{
549: $objects = $obj->loadAllByAttribute($this->getRelatedClassField(), $this->getValue());
550: }
551:
552: return $objects;
553: }else{ // MANY-TO-MANY
554: if(empty($this->lookup)) {
555: throw new AlphaException('Tried to load related MANY-TO-MANY objects but no RelationLookup set on the Relation object!');
556: }
557:
558: if(empty($accessingClassName)) {
559: throw new IllegalArguementException('Tried to load related MANY-TO-MANY objects but no accessingClassName parameter set on the call to getRelatedObjects!');
560: }
561:
562: $objects = array();
563:
564: // load objects on the right from accessing on the left
565: if($accessingClassName == $this->relatedClassLeft) {
566: AlphaDAO::loadClassDef($this->relatedClassRight);
567:
568: $lookupObjects = $this->lookup->loadAllByAttribute('leftID', $this->value);
569:
570: foreach($lookupObjects as $lookupObject) {
571: $obj = new $this->relatedClassRight;
572: $obj->load($lookupObject->get('rightID'));
573: array_push($objects, $obj);
574: }
575: }
576: // load objects on the left from accessing on the right
577: if($accessingClassName == $this->relatedClassRight) {
578: AlphaDAO::loadClassDef($this->relatedClassLeft);
579:
580: $lookupObjects = $this->lookup->loadAllByAttribute('rightID', $this->value);
581:
582: foreach($lookupObjects as $lookupObject) {
583: $obj = new $this->relatedClassLeft;
584: $obj->load($lookupObject->get('leftID'));
585: array_push($objects, $obj);
586: }
587: }
588:
589: return $objects;
590: }
591: }
592:
593: /**
594: * For one-to-one relations, get the object on the other side
595: *
596: * @return array
597: * @since 1.0
598: * @throws AlphaException
599: */
600: public function getRelatedObject() {
601: global $config;
602:
603: try {
604: AlphaDAO::loadClassDef($this->relatedClass);
605: }catch(IllegalArguementException $e) {
606: throw new AlphaException('Could not load the definition for the BO class ['.$this->relatedClass.']');
607: }
608:
609: $obj = new $this->relatedClass;
610: $obj->loadByAttribute($this->getRelatedClassField(), $this->getValue());
611:
612: return $obj;
613: }
614:
615: /**
616: * Get the allowable size of the Relation in the database field
617: *
618: * @return integer
619: * @since 1.0
620: */
621: public function getSize() {
622: return $this->size;
623: }
624:
625: /**
626: * Get the lookup object if available (only on MANY-TO-MANY relations, null otherwise)
627: *
628: * @return RelationLookup
629: * @since 1.0
630: */
631: public function getLookup() {
632: return $this->lookup;
633: }
634:
635: /**
636: * Gets the side ('left' or 'right') of the passed classname on the current Relation object
637: *
638: * @param string $BOClassname
639: * @return string
640: * @since 1.0
641: * @throws IllegalArguementException
642: */
643: public function getSide($BOClassname) {
644: if($BOClassname == $this->relatedClassLeft) {
645: return 'left';
646: }elseif($BOClassname == $this->relatedClassRight) {
647: return 'right';
648: }else{
649: throw new IllegalArguementException('Error trying to determine the MANY-TO-MANY relationship side for the classname ['.$BOClassname.']');
650: }
651: }
652:
653: /**
654: * Set the taggedClass property to the name of the tagged class when building relations
655: * to the TagObject BO.
656: *
657: * @param $taggedClass
658: * @since 1.0
659: */
660: public function setTaggedClass($taggedClass) {
661: $this->taggedClass = $taggedClass;
662: }
663: }
664:
665: ?>