/**
  * Create a new PolymorphicHasManyList relation list.
  *
  * @param string $dataClass The class of the DataObjects that this will list.
  * @param string $foreignField The name of the composite foreign relation field. Used
  * to generate the ID and Class foreign keys.
  * @param string $foreignClass Name of the class filter this relation is filtered against
  */
 function __construct($dataClass, $foreignField, $foreignClass)
 {
     // Set both id foreign key (as in HasManyList) and the class foreign key
     parent::__construct($dataClass, "{$foreignField}ID");
     $this->classForeignKey = "{$foreignField}Class";
     // Ensure underlying DataQuery globally references the class filter
     $this->dataQuery->setQueryParam('Foreign.Class', $foreignClass);
     // For queries with multiple foreign IDs (such as that generated by
     // DataList::relation) the filter must be generalised to filter by subclasses
     $classNames = Convert::raw2sql(ClassInfo::subclassesFor($foreignClass));
     $this->dataQuery->where(sprintf("\"{$this->classForeignKey}\" IN ('%s')", implode("', '", $classNames)));
 }
 /**
  * Given a relation declared on a remote class, generate a substitute component for the opposite
  * side of the relation.
  *
  * Notes on behaviour:
  *  - This can still be used on components that are defined on both sides, but do not need to be.
  *  - All has_ones on remote class will be treated as local has_many, even if they are belongs_to
  *  - Cannot be used on polymorphic relationships
  *  - Cannot be used on unsaved objects.
  *
  * @param string $remoteClass
  * @param string $remoteRelation
  * @return DataList|DataObject The component, either as a list or single object
  * @throws BadMethodCallException
  * @throws InvalidArgumentException
  */
 public function inferReciprocalComponent($remoteClass, $remoteRelation)
 {
     $remote = DataObject::singleton($remoteClass);
     $class = $remote->getRelationClass($remoteRelation);
     $schema = static::getSchema();
     // Validate arguments
     if (!$this->isInDB()) {
         throw new BadMethodCallException(__METHOD__ . " cannot be called on unsaved objects");
     }
     if (empty($class)) {
         throw new InvalidArgumentException(sprintf("%s invoked with invalid relation %s.%s", __METHOD__, $remoteClass, $remoteRelation));
     }
     if ($class === self::class) {
         throw new InvalidArgumentException(sprintf("%s cannot generate opposite component of relation %s.%s as it is polymorphic. " . "This method does not support polymorphic relationships", __METHOD__, $remoteClass, $remoteRelation));
     }
     if (!is_a($this, $class, true)) {
         throw new InvalidArgumentException(sprintf("Relation %s on %s does not refer to objects of type %s", $remoteRelation, $remoteClass, static::class));
     }
     // Check the relation type to mock
     $relationType = $remote->getRelationType($remoteRelation);
     switch ($relationType) {
         case 'has_one':
             // Mock has_many
             $joinField = "{$remoteRelation}ID";
             $componentClass = $schema->classForField($remoteClass, $joinField);
             $result = HasManyList::create($componentClass, $joinField);
             if ($this->model) {
                 $result->setDataModel($this->model);
             }
             return $result->setDataQueryParam($this->getInheritableQueryParams())->forForeignID($this->ID);
         case 'belongs_to':
         case 'has_many':
             // These relations must have a has_one on the other end, so find it
             $joinField = $schema->getRemoteJoinField($remoteClass, $remoteRelation, $relationType, $polymorphic);
             if ($polymorphic) {
                 throw new InvalidArgumentException(sprintf("%s cannot generate opposite component of relation %s.%s, as the other end appears" . "to be a has_one polymorphic. This method does not support polymorphic relationships", __METHOD__, $remoteClass, $remoteRelation));
             }
             $joinID = $this->getField($joinField);
             if (empty($joinID)) {
                 return null;
             }
             // Get object by joined ID
             return DataObject::get($remoteClass)->filter('ID', $joinID)->setDataQueryParam($this->getInheritableQueryParams())->first();
         case 'many_many':
         case 'belongs_many_many':
             // Get components and extra fields from parent
             list($relationClass, $componentClass, $parentClass, $componentField, $parentField, $table) = $remote->getSchema()->manyManyComponent($remoteClass, $remoteRelation);
             $extraFields = $schema->manyManyExtraFieldsForComponent($remoteClass, $remoteRelation) ?: array();
             // Reverse parent and component fields and create an inverse ManyManyList
             /** @var RelationList $result */
             $result = Injector::inst()->create($relationClass, $componentClass, $table, $componentField, $parentField, $extraFields);
             if ($this->model) {
                 $result->setDataModel($this->model);
             }
             $this->extend('updateManyManyComponents', $result);
             // If this is called on a singleton, then we return an 'orphaned relation' that can have the
             // foreignID set elsewhere.
             return $result->setDataQueryParam($this->getInheritableQueryParams())->forForeignID($this->ID);
         default:
             return null;
     }
 }
 /**
  * Get has_many relationship between parent and join table (for a given DataQuery)
  *
  * @param DataQuery $query
  * @return HasManyList
  */
 public function getParentRelationship(DataQuery $query)
 {
     // Create has_many
     $list = HasManyList::create($this->getJoinClass(), $this->getForeignKey());
     $list = $list->setDataQueryParam($this->extractInheritableQueryParameters($query));
     // Limit to given foreign key
     if ($foreignID = $query->getQueryParam('Foreign.ID')) {
         $list = $list->forForeignID($foreignID);
     }
     return $list;
 }