Validates & completes a many-to-many association mapping.

@param array $mapping The mapping to validate & complete.

@return array The validated & completed mapping.

@throws \InvalidArgumentException
186634
protected function _validateAndCompleteManyToManyMapping(array $mapping)
    {
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
        if ($mapping['isOwningSide']) {
            // owning side MUST have a join table
            if ( ! isset($mapping['joinTable']['name'])) {
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
            }

            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));

            if ( ! isset($mapping['joinTable']['joinColumns'])) {
                $mapping['joinTable']['joinColumns'] = array(array(
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
                        'onDelete' => 'CASCADE'));
            }
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
                $mapping['joinTable']['inverseJoinColumns'] = array(array(
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
                        'onDelete' => 'CASCADE'));
            }

            $mapping['joinTableColumns'] = array();

            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
                if (empty($joinColumn['name'])) {
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
                }

                if (empty($joinColumn['referencedColumnName'])) {
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
                }

                if ($joinColumn['name'][0] === '`') {
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
                    $joinColumn['quoted'] = true;
                }

                if ($joinColumn['referencedColumnName'][0] === '`') {
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
                    $joinColumn['quoted']               = true;
                }

                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
                    $mapping['isOnDeleteCascade'] = true;
                }

                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
                $mapping['joinTableColumns'][] = $joinColumn['name'];
            }

            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
                if (empty($inverseJoinColumn['name'])) {
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
                }

                if (empty($inverseJoinColumn['referencedColumnName'])) {
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
                }

                if ($inverseJoinColumn['name'][0] === '`') {
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
                    $inverseJoinColumn['quoted'] = true;
                }

                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
                    $inverseJoinColumn['quoted']                = true;
                }

                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
                    $mapping['isOnDeleteCascade'] = true;
                }

                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
            }
        }

        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;

        if (isset($mapping['orderBy'])) {
            if ( ! is_array($mapping['orderBy'])) {
                throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
            }
        }

        return $mapping;
    }
622519
public function mapOneToOne(array $mapping)
    {
        $mapping['type'] = self::ONE_TO_ONE;
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
        $this->_storeAssociationMapping($mapping);
    }
192349
public function validateClassMapping()
    {
        // associative array fields need a separate property to store the keys.
        // make sure that generated or specified name does not collide with an
        // existing mapping.
        $assocFields = [];
        foreach ($this->fieldMappings as $fieldName) {
            $mapping = $this->mappings[$fieldName];
            if (empty($mapping['assoc'])) {
                continue;
            }

            if (isset($this->mappings[$mapping['assoc']])) {
                throw MappingException::assocOverlappingFieldDefinition($this->name, $fieldName, $mapping['assoc']);
            }

            if (!empty($assocFields[$mapping['assoc']])) {
                throw MappingException::assocOverlappingAssocDefinition($this->name, $fieldName, $assocFields[$mapping['assoc']]);
            }

            $assocFields[$mapping['assoc']] = $fieldName;
        }

        if (!empty($this->versionNameField) && !$this->versionable) {
            throw new MappingException(sprintf(
                'You cannot use the @VersionName annotation on the non-versionable document %s (field = %s)',
                $this->name,
                $this->versionNameField
            ));
        }

        if (!empty($this->versionCreatedField) && !$this->versionable) {
            throw new MappingException(sprintf(
                'You cannot use the @VersionCreated annotation on the non-versionable document %s (field = %s)',
                $this->name,
                $this->versionCreatedField
            ));
        }

        if (count($this->translatableFields)) {
            if (!isset($this->localeMapping)) {
                throw new MappingException(sprintf(
                    'You must define a locale mapping for translatable document "%s"',
                    $this->name
                ));
            }
        }

        // we allow mixed referrers on non-referenceable documents. maybe the mix:referenceable is just not mapped
        if (count($this->referrersMappings)) {
            if (!$this->referenceable) {
                throw new MappingException(sprintf(
                    'You can not have referrers mapped on document "%s" as the document is not referenceable',
                    $this->name
                ));
            }

            foreach ($this->referrersMappings as $referrerName) {
                $mapping = $this->mappings[$referrerName];
                // only a santiy check with reflection. otherwise we could run into endless loops
                if (!ClassLoader::classExists($mapping['referringDocument'])) {
                    throw new MappingException(sprintf(
                        'Invalid referrer mapping on document "%s" for field "%s": The referringDocument class "%s" does not exist',
                        $this->name,
                        $mapping['fieldName'],
                        $mapping['referringDocument']
                    ));
                }
                $reflection = new ReflectionClass($mapping['referringDocument']);
                if (!$reflection->hasProperty($mapping['referencedBy'])) {
                    throw new MappingException(sprintf(
                        'Invalid referrer mapping on document "%s" for field "%s": The referringDocument "%s" has no property "%s"',
                        $this->name,
                        $mapping['fieldName'],
                        $mapping['referringDocument'],
                        $mapping['referencedBy']
                    ));
                }
            }
        }

        $this->validateIdentifier();
    }
989149
public function mapManyToOne(array $mapping)
    {
        $mapping['type'] = self::MANY_TO_ONE;
        // A many-to-one mapping is essentially a one-one backreference
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
        $this->_storeAssociationMapping($mapping);
    }
859716
public function mapParameter(array $mapping)
    {
        if (isset($this->fieldMappings[$mapping['parameterName']])) {
            throw MappingException::duplicateParameterMapping($this->className, $mapping['parameterName']);
        }
        $this->parameters[$mapping['parameterName']] = $mapping;
    }
112401
public function parseMappedColumn($mapping)
    {
        $segments = explode('.', $mapping);

        $column = array_pop($segments);

        $target = implode('.', $segments);

        return [$target, $column];
    }
759030
private function getSerializerFields(\DOMElement $mapping)
    {
        $xpath = new \DOMXPath($mapping->ownerDocument);

        return array_map(
            function (\DOMElement $element) {
                return [
                    'fieldName'   => $element->getAttribute('name'),
                    'fieldType'   => $this->getSerializerFieldType($element),
                    'exposedName' => $element->getAttribute('serialized-name') ?: $element->getAttribute('name'),
                    'readOnly'    => $element->getAttribute('read-only') === 'true',
                    'searchable'  => (int) $element->getAttribute('searchable')
                ];
            },
            iterator_to_array($xpath->query('property', $mapping))
        );
    }
72423
protected function cacheMapping($mapping)
    {
        $mapping = array_filter($mapping);
        if ($mapping && !empty($mapping)) {
            $cache = SS_Cache::factory('gridfieldimporter');
            $cache->save(serialize($mapping), $this->cacheKey());
        }
    }
905787
protected function validateAndCompleteManyToOneMapping(ManyToOneAssociationMetadata $property)
    {
        // A many-to-one mapping is essentially a one-one backreference
        if ($property->isOrphanRemoval()) {
            throw MappingException::illegalOrphanRemoval($this->className, $property->getName());
        }
    }
1215870
public function addMapping(PathMapping $mapping)
    {
        if (!$mapping->isLoaded()) {
            throw new NotLoadedException('The passed mapping must be loaded.');
        }

        $moduleName = $mapping->getContainingModule()->getName();
        $previousMapping = isset($this->mappings[$moduleName]) ? $this->mappings[$moduleName] : null;

        if ($previousMapping === $mapping) {
            return;
        }

        if ($previousMapping) {
            $previousMapping->removeConflict($this);
        }

        $this->mappings[$moduleName] = $mapping;
        $mapping->addConflict($this);
    }
981910
public function manyPropertyModelForTarget($mapping)
    {
        // @todo refactor to get rid of container dependency (maybe remove from here)
        list($app, $bundle, , $document) = explode('\\', $mapping);
        $app = strtolower($app);
        $bundle = strtolower(substr($bundle, 0, -6));
        $document = strtolower($document);
        $propertyService = implode('.', array($app, $bundle, 'model', $document));
        $propertyModel = $this->container->get($propertyService);

        return $propertyModel;
    }
487340
public function getArguments(array $mapping, $data, ResolveInfo $info)
    {
        $args = [];
        $exceptions = [];

        foreach ($mapping as $name => $type) {
            try {
                $value = $this->getInstanceAndValidate($type, $data[$name], $info, $name);
                $args[] = $value;
            } catch (InvalidArgumentError $exception) {
                $exceptions[] = $exception;
            }
        }

        if (!empty($exceptions)) {
            throw new InvalidArgumentsError($exceptions);
        }

        return $args;
    }
768823
public function mapEmbedded(array $mapping)
    {
        $this->assertFieldNotMapped($mapping['fieldName']);

        $this->embeddedClasses[$mapping['fieldName']] = array(
            'class' => $this->fullyQualifiedClassName($mapping['class']),
            'columnPrefix' => $mapping['columnPrefix'],
            'declaredField' => isset($mapping['declaredField']) ? $mapping['declaredField'] : null,
            'originalField' => isset($mapping['originalField']) ? $mapping['originalField'] : null,
        );
    }
244954
private function checkMapNames(array $map): void
    {
        $names = array_keys($map);
        $bad_names = preg_grep(self::RE_NAME, $names, PREG_GREP_INVERT);
        if ($bad_names) {
            throw new \InvalidArgumentException(
                'Bad names: ' . join(', ', $bad_names)
            );
        }
    }
211338
private function buildMapping($mapping)
    {
        foreach ($mapping as $field => $type) {
            $mapping[$field] = $this->buildType($type);
        }

        return $mapping;
    }
158679
public function mapField(array $mapping)
    {
        $mapping = parent::mapField($mapping);

        if ($this->reflClass->hasProperty($mapping['fieldName'])) {
            $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
            $reflProp->setAccessible(true);
            $this->reflFields[$mapping['fieldName']] = $reflProp;
        }
    }
260080
protected function setMappings(array $mappings = array())
    {
        foreach ($mappings as $mapping) {
            if (!$mapping instanceof ImsManifestMapping) {
                $msg = "The mappings argument must be an array composed of ImsManifestMapping objects";
                throw new InvalidArgumentException($msg);
            }
        }

        $this->mappings = $mappings;
    }