      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: ?>