/** * @param Request $request * @param FilterInterface $filter * @param Criteria $criteria * @param ClassMetadata $embedClassMeta * * @return null */ protected function applyFilter(Request $request, FilterInterface $filter, Criteria $criteria, ClassMetadata $embedClassMeta) { $properties = $filter->getRequestProperties($request); if ($filter instanceof OrderFilter && !empty($properties)) { $criteria->orderBy($properties); return null; } if ($filter instanceof SearchFilter) { foreach ($properties as $name => $propertie) { if (in_array($name, $embedClassMeta->getIdentifier())) { continue; } $expCriterial = Criteria::expr(); if ($embedClassMeta->hasAssociation($name)) { $associationTargetClass = $embedClassMeta->getAssociationTargetClass($name); $propertyResource = $this->resourceResolver->getResourceForEntity($associationTargetClass); $propertyObj = $this->dataProviderChain->getItem($propertyResource, (int) $propertie['value'], true); if ($propertyObj && $propertyResource instanceof ResourceInterface) { $whereCriteria = $expCriterial->in($name, [$propertyObj]); $criteria->where($whereCriteria); } } else { if ($embedClassMeta->hasField($name)) { $fieldMapping = $embedClassMeta->getFieldMapping($name); $type = isset($fieldMapping['type']) ? $fieldMapping['type'] : null; $value = isset($this->mappingFilterVar[$type]) ? filter_var($propertie['value'], $this->mappingFilterVar[$type]) : $propertie['value']; $whereCriteria = isset($propertie['precision']) && $propertie['precision'] === 'exact' ? $expCriterial->eq($name, $value) : $expCriterial->contains($name, $propertie['value']); $criteria->where($whereCriteria); } } } } }
public function guessColumnFormatters(\Faker\Generator $generator) { $formatters = array(); $class = $this->class; $nameGuesser = new \Faker\Guesser\Name($generator); $columnTypeGuesser = new ColumnTypeGuesser($generator); foreach ($this->class->getFieldNames() as $fieldName) { if ($this->class->isIdentifier($fieldName) || !$this->class->hasField($fieldName)) { continue; } if ($formatter = $nameGuesser->guessFormat($fieldName)) { $formatters[$fieldName] = $formatter; continue; } if ($formatter = $columnTypeGuesser->guessFormat($fieldName, $this->class)) { $formatters[$fieldName] = $formatter; continue; } } foreach ($this->class->getAssociationNames() as $assocName) { if (!$this->class->isIdentifier($assocName) || !$this->class->isCollectionValuedAssociation($assocName)) { continue; } $relatedClass = $this->class->getAssociationTargetClass($assocName); $formatters[$assocName] = function ($inserted) use($relatedClass) { return isset($inserted[$relatedClass]) ? $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)] : null; }; } return $formatters; }
/** * Hydrate $object with the provided $data. * * @param array $data * @param object $object * @throws \Exception * @return object */ public function hydrate(array $data, $object) { $this->metadata = $this->objectManager->getClassMetadata(get_class($object)); foreach ($data as $field => &$value) { if ($this->metadata->hasAssociation($field)) { $target = $this->metadata->getAssociationTargetClass($field); if ($this->metadata->isSingleValuedAssociation($field)) { $value = $this->toOne($value, $target); } elseif ($this->metadata->isCollectionValuedAssociation($field)) { $value = $this->toMany($value, $target); } } } return $this->hydrator->hydrate($data, $object); }
private function getAssociationString(ClassMetadata $class1, $association) { $targetClassName = $class1->getAssociationTargetClass($association); $class2 = $this->getClassByName($targetClassName); $isInverse = $class1->isAssociationInverseSide($association); $class1Count = $class1->isCollectionValuedAssociation($association) ? 2 : 1; if (null === $class2) { return $this->getClassString($class1) . ($isInverse ? '<' : '<>') . '-' . $association . ' ' . ($class1Count > 1 ? '*' : ($class1Count ? '1' : '')) . ($isInverse ? '<>' : '>') . '[' . str_replace('\\', '.', $targetClassName) . ']'; } $class1SideName = $association; $class2SideName = ''; $class2Count = 0; $bidirectional = false; if ($isInverse) { $class2SideName = (string) $class1->getAssociationMappedByTargetField($association); if ($class2SideName) { $class2Count = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1; $bidirectional = true; } } else { foreach ($class2->getAssociationNames() as $class2Side) { if ($class2->isAssociationInverseSide($class2Side) && $class2->getAssociationMappedByTargetField($class2Side) === $association) { $class2SideName = $class2Side; $class2Count = $class2->isCollectionValuedAssociation($class2SideName) ? 2 : 1; $bidirectional = true; break; } } } $this->visitAssociation($targetClassName, $class2SideName); return $this->getClassString($class1) . ($bidirectional ? $isInverse ? '<' : '<>' : '') . ($class2SideName ? $class2SideName . ' ' : '') . ($class2Count > 1 ? '*' : ($class2Count ? '1' : '')) . '-' . $class1SideName . ' ' . ($class1Count > 1 ? '*' : ($class1Count ? '1' : '')) . ($bidirectional && $isInverse ? '<>' : '>') . $this->getClassString($class2); }
/** * Hydrate $object with the provided $data. * * @param array $data * @param object $object * @throws \Exception * @return object */ public function hydrate(array $data, $object) { $this->metadata = $this->objectManager->getClassMetadata(get_class($object)); $object = $this->tryConvertArrayToObject($data, $object); foreach ($data as $field => &$value) { $value = $this->hydrateValue($field, $value); if ($value === null) { continue; } // @todo DateTime (and other types) conversion should be handled by doctrine itself in future if (in_array($this->metadata->getTypeOfField($field), array('datetime', 'time', 'date'))) { if (is_int($value)) { $dt = new DateTime(); $dt->setTimestamp($value); $value = $dt; } elseif (is_string($value)) { $value = new DateTime($value); } } if ($this->metadata->hasAssociation($field)) { $target = $this->metadata->getAssociationTargetClass($field); if ($this->metadata->isSingleValuedAssociation($field)) { $value = $this->toOne($value, $target); } elseif ($this->metadata->isCollectionValuedAssociation($field)) { $value = $this->toMany($value, $target); // Automatically merge collections using helper utility $propertyRefl = $this->metadata->getReflectionClass()->getProperty($field); $propertyRefl->setAccessible(true); $previousValue = $propertyRefl->getValue($object); $value = CollectionUtils::intersectUnion($previousValue, $value); } } } return $this->hydrator->hydrate($data, $object); }
protected function setPropertyType(DoctrineClassMetadata $doctrineMetadata, PropertyMetadata $propertyMetadata) { /** @var \Doctrine\ODM\PHPCR\Mapping\ClassMetadata $doctrineMetadata */ $propertyName = $propertyMetadata->name; if ($doctrineMetadata->hasField($propertyName) && ($fieldType = $this->normalizeFieldType($doctrineMetadata->getTypeOfField($propertyName)))) { $field = $doctrineMetadata->getFieldMapping($propertyName); if (!empty($field['multivalue'])) { $fieldType = 'array'; } $propertyMetadata->setType($fieldType); } elseif ($doctrineMetadata->hasAssociation($propertyName)) { try { $targetEntity = $doctrineMetadata->getAssociationTargetClass($propertyName); } catch (\Exception $e) { return; } if (null === $this->tryLoadingDoctrineMetadata($targetEntity)) { return; } if (!$doctrineMetadata->isSingleValuedAssociation($propertyName)) { $targetEntity = "ArrayCollection<{$targetEntity}>"; } $propertyMetadata->setType($targetEntity); } }
/** * Updates a single entity association * * @param string $propertyName * @param array $propertyValue * @param object $resource */ protected function updateEntityAssociation($propertyName, array $propertyValue, $resource, ClassMetadata $entityMetadata) { if ($entityMetadata->isSingleValuedAssociation($propertyName)) { $associationTargetClass = $entityMetadata->getAssociationTargetClass($propertyName); $repository = $this->getRepositoryByTargetClass($associationTargetClass); $associationResource = $repository->findOneBy($propertyValue); if (null !== $associationResource && $this->propertyAccessor->isWritable($resource, $propertyName)) { $this->propertyAccessor->setValue($resource, $propertyName, $associationResource); } } }
public function __construct(ObjectManager $om, ClassMetadata $classMetadata) { $ids = $classMetadata->getIdentifierFieldNames(); $idType = $classMetadata->getTypeOfField(current($ids)); $this->om = $om; $this->classMetadata = $classMetadata; $this->singleId = 1 === count($ids); $this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint')); $this->idField = current($ids); // single field association are resolved, since the schema column could be an int if ($this->singleId && $classMetadata->hasAssociation($this->idField)) { $this->associationIdReader = new self($om, $om->getClassMetadata($classMetadata->getAssociationTargetClass($this->idField))); $this->singleId = $this->associationIdReader->isSingleId(); $this->intId = $this->associationIdReader->isIntId(); } }
/** * @param ClassMetadata $metadata * @param array $assocNames * * @return array */ private function getAssocsConfig(ClassMetadata $metadata, $assocNames) { $assocsConfigs = []; foreach ($assocNames as $assocName) { if (!$metadata->isAssociationInverseSide($assocName)) { continue; } $class = $metadata->getAssociationTargetClass($assocName); if ($metadata->isSingleValuedAssociation($assocName)) { $nullable = $metadata instanceof ClassMetadataInfo && isset($metadata->discriminatorColumn['nullable']) && $metadata->discriminatorColumn['nullable']; $assocsConfigs[$assocName] = ['field_type' => 'A2lix\\AutoFormBundle\\Form\\Type\\AutoFormType', 'data_class' => $class, 'required' => !$nullable]; continue; } $assocsConfigs[$assocName] = ['field_type' => 'Symfony\\Component\\Form\\Extension\\Core\\Type\\CollectionType', 'entry_type' => 'A2lix\\AutoFormBundle\\Form\\Type\\AutoFormType', 'entry_options' => ['data_class' => $class], 'allow_add' => true, 'by_reference' => false]; } return $assocsConfigs; }
/** * Adds an object to a collection. * * @param string $field * @param array $args * * @return void * * @throws \BadMethodCallException * @throws \InvalidArgumentException */ private function add($field, $args) { $this->initializeDoctrine(); if ($this->cm->hasAssociation($field) && $this->cm->isCollectionValuedAssociation($field)) { $targetClass = $this->cm->getAssociationTargetClass($field); if (!$args[0] instanceof $targetClass) { throw new \InvalidArgumentException("Expected persistent object of type '" . $targetClass . "'"); } if (!$this->{$field} instanceof Collection) { $this->{$field} = new ArrayCollection($this->{$field} ?: []); } $this->{$field}->add($args[0]); $this->completeOwningSide($field, $targetClass, $args[0]); } else { throw new \BadMethodCallException("There is no method add" . $field . "() on " . $this->cm->getName()); } }
/** * Creates a new entity choice list. * * @param ObjectManager $manager An EntityManager instance * @param string $class The class name * @param string $labelPath The property path used for the label * @param EntityLoaderInterface $entityLoader An optional query builder * @param array|\Traversable|null $entities An array of choices or null to lazy load * @param array $preferredEntities An array of preferred choices * @param string $groupPath A property path pointing to the property used * to group the choices. Only allowed if * the choices are given as flat array. * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. */ public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null) { $this->em = $manager; $this->entityLoader = $entityLoader; $this->classMetadata = $manager->getClassMetadata($class); $this->class = $this->classMetadata->getName(); $this->loaded = is_array($entities) || $entities instanceof \Traversable; $this->preferredEntities = $preferredEntities; list($this->idAsIndex, $this->idAsValue, $this->idField) = $this->getIdentifierInfoForClass($this->classMetadata); if (null !== $this->idField && $this->classMetadata->hasAssociation($this->idField)) { $this->idClassMetadata = $this->em->getClassMetadata($this->classMetadata->getAssociationTargetClass($this->idField)); list($this->idAsIndex, $this->idAsValue) = $this->getIdentifierInfoForClass($this->idClassMetadata); } if (!$this->loaded) { // Make sure the constraints of the parent constructor are // fulfilled $entities = array(); } parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor); }
protected function setPropertyType(DoctrineClassMetadata $doctrineMetadata, PropertyMetadata $propertyMetadata) { $propertyName = $propertyMetadata->name; if ($doctrineMetadata->hasField($propertyName) && ($fieldType = $this->normalizeFieldType($doctrineMetadata->getTypeOfField($propertyName)))) { $propertyMetadata->setType($fieldType); } elseif ($doctrineMetadata->hasAssociation($propertyName)) { $targetEntity = $doctrineMetadata->getAssociationTargetClass($propertyName); if (null === ($targetMetadata = $this->tryLoadingDoctrineMetadata($targetEntity))) { return; } // For inheritance schemes, we cannot add any type as we would only add the super-type of the hierarchy. // On serialization, this would lead to only the supertype being serialized, and properties of subtypes // being ignored. if ($targetMetadata instanceof DoctrineClassMetadata && !$targetMetadata->isInheritanceTypeNone()) { return; } if (!$doctrineMetadata->isSingleValuedAssociation($propertyName)) { $targetEntity = "ArrayCollection<{$targetEntity}>"; } $propertyMetadata->setType($targetEntity); } }
/** * @param ClassMetadata $class1 * @param ClassMetadata $class2 * @return string|null */ private function getClassReverseAssociationName(ClassMetadata $class1, ClassMetadata $class2) { foreach ($class2->getAssociationNames() as $class2Side) { $targetClass = $this->getClassByName($class2->getAssociationTargetClass($class2Side)); if ($class1->getName() === $targetClass->getName()) { return $class2Side; } } return null; }
/** * @return ClassMetadata */ public function getTranslationMetadata() { $associationName = $this->associationMetadata->getAssociationName(); $translationClass = $this->classMetadata->getAssociationTargetClass($associationName); return $this->objectManager->getClassMetadata($translationClass); }
protected function addAssociationsTargetClasses(ClassMetadata $metadata, array &$classes) { $associationNames = $metadata->getAssociationNames(); foreach ($associationNames as $associationName) { $associationClass = $metadata->getAssociationTargetClass($associationName); $classes[$associationClass] = $associationClass; } }
/** * Builds the association value. * * @param ClassMetadata $metadata * @param string $propertyPath * @param string $value * * @return array|object * @throws \Exception */ private function buildAssociationValue(ClassMetadata $metadata, $propertyPath, $value) { $childMetadata = $this->manager->getClassMetadata($metadata->getAssociationTargetClass($propertyPath)); // Single association if ($metadata->isSingleValuedAssociation($propertyPath)) { if (is_string($value) && '#' === substr($value, 0, 1)) { return $this->getReference(substr($value, 1)); } elseif (is_array($value)) { return $this->buildEntity($childMetadata, $value); } throw new \Exception("Unexpected value for single association '{$propertyPath}'."); // Collection association } elseif ($metadata->isCollectionValuedAssociation($propertyPath)) { if (!is_array($value)) { throw new \Exception('Expected array.'); } $builtValue = []; foreach ($value as $childData) { if (is_string($childData) && '#' === substr($childData, 0, 1)) { array_push($builtValue, $this->getReference(substr($childData, 1))); } elseif (is_array($value)) { array_push($builtValue, $this->buildEntity($childMetadata, $childData)); } else { throw new \Exception("Unexpected value for association '{$propertyPath}'."); } } return $builtValue; } throw new \Exception("Unexpected association path '{$propertyPath}'."); }
/** * Helper method to insert, remove or update translations entities associated with specified object * * @param \Doctrine\Common\Persistence\ObjectManager $objectManager * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $meta * @param \FSi\DoctrineExtensions\Translatable\Mapping\ClassMetadata $translatableMeta * @param object $object */ protected function updateTranslations(ObjectManager $objectManager, ClassMetadata $meta, TranslatableClassMetadata $translatableMeta, $object) { $localeProperty = $translatableMeta->localeProperty; $propertyObserver = $this->getPropertyObserver($objectManager); $locale = $objectLocale = PropertyAccess::createPropertyAccessor()->getValue($object, $localeProperty); $objectLanguageChanged = !$propertyObserver->hasSavedValue($object, $localeProperty) && isset($objectLocale) || $propertyObserver->hasSavedValue($object, $localeProperty) && $propertyObserver->hasValueChanged($object, $localeProperty); if (!isset($locale)) { $locale = $this->getLocale(); } $translatableProperties = $translatableMeta->getTranslatableProperties(); foreach ($translatableProperties as $translation => $properties) { $translationEntity = $meta->getAssociationTargetClass($translation); $translationMeta = $objectManager->getClassMetadata($translationEntity); $translationLanguageField = $this->getTranslationLanguageField($objectManager, $translationMeta->name); $translationAssociation = $meta->getAssociationMapping($translation); $translations = $meta->getFieldValue($object, $translation); $currentTranslation = null; if (isset($translations)) { $currentTranslation = $this->findTranslation($translations, $translationMeta, $translationLanguageField, $locale); } $propertiesFound = false; foreach ($properties as $property => $translationField) { $propertyValue = PropertyAccess::createPropertyAccessor()->getValue($object, $property); if (isset($propertyValue)) { $propertiesFound = true; } if ($objectLanguageChanged || !$propertyObserver->hasSavedValue($object, $property) && isset($propertyValue) || $propertyObserver->hasSavedValue($object, $property) && $propertyObserver->hasValueChanged($object, $property)) { if (isset($propertyValue)) { if (!isset($currentTranslation)) { $currentTranslation = new $translationEntity(); $translationMeta->setFieldValue($currentTranslation, $translationLanguageField, $locale); $translationMeta->setFieldValue($currentTranslation, $translationAssociation['mappedBy'], $object); if (isset($translationAssociation['indexBy'])) { $index = $translationMeta->getFieldValue($currentTranslation, $translationAssociation['indexBy']); $translations[$index] = $currentTranslation; } else { $translations[] = $currentTranslation; } $objectManager->persist($currentTranslation); } $translationMeta->setFieldValue($currentTranslation, $translationField, $propertyValue); } else { if ($currentTranslation) { $translationMeta->setFieldValue($currentTranslation, $translationField, null); } } } } if ($propertiesFound && !isset($locale)) { throw new Exception\RuntimeException('Neither object\'s locale nor the default locale was defined for translatable properties'); } if (!$propertiesFound && isset($currentTranslation) && isset($objectLocale)) { $objectManager->remove($currentTranslation); if ($translations->contains($currentTranslation)) { $translations->removeElement($currentTranslation); } } } }
protected function getTargetClass(ClassMetadata $metadata, $data, $field) { $mapping = $metadata->fieldMappings[$field]; if (isset($mapping['discriminatorMap'])) { $discriminatorField = isset($mapping['discriminatorField']) ? $mapping['discriminatorField'] : '_doctrine_class_name'; $targetClass = $mapping['discriminatorMap'][$data[$discriminatorField]]; } elseif (isset($mapping['targetDocument']) && class_exists($mapping['targetDocument'])) { $targetClass = $mapping['targetDocument']; } else { $targetClass = $metadata->getAssociationTargetClass($field); } return $targetClass; }
protected function addDependentFixtureInterface(PhpClass $class, ClassMetadata $metadata, array $options) { $class->addInterfaceName('Doctrine\\Common\\DataFixtures\\DependentFixtureInterface'); $writer = new Writer(); $method = PhpMethod::create('getDependencies'); $writer->writeln("return array("); $associations = array(); foreach ($metadata->getAssociationNames() as $assocName) { $targetClass = $metadata->getAssociationTargetClass($assocName); $associations[] = sprintf("'%s\\%s'", $options['namespace'], $this->namingStrategy->fixtureName($this->getManager()->getClassMetadata($targetClass))); } $writer->indent(); $writer->writeln(implode(",\n", $associations)); $writer->outdent(); $writer->writeln(");"); $method->setBody($writer->getContent()); $class->setMethod($method); }
/** * @param ClassMetadata $targetEntity * @param string $targetEntityPropertyName * @return Query */ protected function getSubselectQuery(ClassMetadata $targetEntity, $targetEntityPropertyName) { $subselectQuery = new Query($targetEntity->getAssociationTargetClass($targetEntityPropertyName)); $propertyName = str_replace($targetEntityPropertyName . '.', '', $this->path); switch ($this->operator) { case '==': $subselectConstraint = $subselectQuery->equals($propertyName, $this->operand); break; case '!=': $subselectConstraint = $subselectQuery->logicalNot($subselectQuery->equals($propertyName, $this->operand)); break; case '<': $subselectConstraint = $subselectQuery->lessThan($propertyName, $this->operand); break; case '>': $subselectConstraint = $subselectQuery->greaterThan($propertyName, $this->operand); break; case '<=': $subselectConstraint = $subselectQuery->lessThanOrEqual($propertyName, $this->operand); break; case '>=': $subselectConstraint = $subselectQuery->greaterThanOrEqual($propertyName, $this->operand); break; case 'like': $subselectConstraint = $subselectQuery->like($propertyName, $this->operand); break; case 'in': $subselectConstraint = $subselectQuery->in($propertyName, $this->operand); break; } $subselectQuery->matching($subselectConstraint); return $subselectQuery; }
/** * Проходит по всем классам в поисках ассоциаций и составляет список этих классов в переменной arrayOfClasses * * @param ClassMetadata $metadata метаданные класса, в котором нужно будет производить поиск ассоциций * * @return array ассоциативный массив, ключами которого являются надйенные классы */ private function scanClassMetadataToGetFullListOfAssociations(ClassMetadata $metadata) { $className = $metadata->getName(); $arrayOfClassesByKey = [$className => true]; $assocNames = $metadata->getAssociationNames(); foreach ($assocNames as $assocName) { if ($metadata->isCollectionValuedAssociation($assocName)) { continue; } $targetClass = $metadata->getAssociationTargetClass($assocName); if (!isset($arrayOfClassesByKey[$targetClass])) { $arrayOfClassesByKey += $this->scanClassMetadataToGetFullListOfAssociations($this->om->getClassMetadata($targetClass)); } } return $arrayOfClassesByKey; }
/** * @param \Faker\Generator $generator * @return array */ public function guessColumnFormatters(\Faker\Generator $generator) { $formatters = array(); $nameGuesser = new \Faker\Guesser\Name($generator); $columnTypeGuesser = new ColumnTypeGuesser($generator); foreach ($this->class->getFieldNames() as $fieldName) { if ($this->class->isIdentifier($fieldName) || !$this->class->hasField($fieldName)) { continue; } $size = isset($this->class->fieldMappings[$fieldName]['length']) ? $this->class->fieldMappings[$fieldName]['length'] : null; if ($formatter = $nameGuesser->guessFormat($fieldName, $size)) { $formatters[$fieldName] = $formatter; continue; } if ($formatter = $columnTypeGuesser->guessFormat($fieldName, $this->class)) { $formatters[$fieldName] = $formatter; continue; } } foreach ($this->class->getAssociationNames() as $assocName) { if ($this->class->isCollectionValuedAssociation($assocName)) { continue; } $relatedClass = $this->class->getAssociationTargetClass($assocName); $unique = $optional = false; if ($this->class instanceof \Doctrine\ORM\Mapping\ClassMetadata) { $mappings = $this->class->getAssociationMappings(); foreach ($mappings as $mapping) { if ($mapping['targetEntity'] == $relatedClass) { if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_ONE) { $unique = true; $optional = isset($mapping['joinColumns'][0]['nullable']) ? $mapping['joinColumns'][0]['nullable'] : false; break; } } } } elseif ($this->class instanceof \Doctrine\ODM\MongoDB\Mapping\ClassMetadata) { $mappings = $this->class->associationMappings; foreach ($mappings as $mapping) { if ($mapping['targetDocument'] == $relatedClass) { if ($mapping['type'] == \Doctrine\ODM\MongoDB\Mapping\ClassMetadata::ONE && $mapping['association'] == \Doctrine\ODM\MongoDB\Mapping\ClassMetadata::REFERENCE_ONE) { $unique = true; $optional = isset($mapping['nullable']) ? $mapping['nullable'] : false; break; } } } } $index = 0; $formatters[$assocName] = function ($inserted) use($relatedClass, &$index, $unique, $optional) { if (isset($inserted[$relatedClass])) { if ($unique) { $related = null; if (isset($inserted[$relatedClass][$index]) || !$optional) { $related = $inserted[$relatedClass][$index]; } $index++; return $related; } return $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)]; } return null; }; } return $formatters; }