/** * 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; }