src/ORM/DataObject.php
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 relation is polymorphic, do not infer recriprocal relationship
        if ($class === self::class) {
            return null;
        }
        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);
                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 relation is polymorphic, do not infer recriprocal relationship automatically
                if ($polymorphic) {
                    return null;
                }
                $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
                $manyMany = $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(
                    $manyMany['relationClass'],
                    $manyMany['parentClass'], // Substitute parent class for dataClass
                    $manyMany['join'],
                    $manyMany['parentField'], // Reversed parent / child field
                    $manyMany['childField'], // Reversed parent / child field
                    $extraFields,
                    $manyMany['childClass'], // substitute child class for parentClass
                    $remoteClass // In case ManyManyThroughList needs to use PolymorphicHasManyList internally
                );
                $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;
            }
        }
    }
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 relation is polymorphic, do not infer recriprocal relationship
        if ($class === self::class) {
            return null;
        }
        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);
                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 relation is polymorphic, do not infer recriprocal relationship automatically
                if ($polymorphic) {
                    return null;
                }
                $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
                $manyMany = $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(
                    $manyMany['relationClass'],
                    $manyMany['parentClass'], // Substitute parent class for dataClass
                    $manyMany['join'],
                    $manyMany['parentField'], // Reversed parent / child field
                    $manyMany['childField'], // Reversed parent / child field
                    $extraFields,
                    $manyMany['childClass'], // substitute child class for parentClass
                    $remoteClass // In case ManyManyThroughList needs to use PolymorphicHasManyList internally
                );
                $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;
            }
        }
    }
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 relation is polymorphic, do not infer recriprocal relationship if ( $ class === self :: class ) { return null ; } 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 ) ; 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 relation is polymorphic, do not infer recriprocal relationship automatically if ( $ polymorphic ) { return null ; } $ 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 $ manyMany = $ 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 ( $ manyMany [ 'relationClass' ] , $ manyMany [ 'parentClass' ] , // Substitute parent class for dataClass $ manyMany [ 'join' ] , $ manyMany [ 'parentField' ] , // Reversed parent / child field $ manyMany [ 'childField' ] , // Reversed parent / child field $ extraFields , $ manyMany [ 'childClass' ] , // substitute child class for parentClass $ remoteClass // In case ManyManyThroughList needs to use PolymorphicHasManyList internally ) ; $ 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 ; } } }
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
- Polymorphic relationships do not have two natural endpoints (only on one side)
and thus attempting to infer them will return nothing.
- 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
Given a relation declared on a remote class generate a substitute component for the opposite side of the relation .