ourField === null) { $this->ourField = $this->link; } $checkTheirTypeOrig = $this->checkTheirType; $this->checkTheirType = false; try { $analysingTheirModel = $this->createAnalysingTheirModel(); } finally { $this->checkTheirType = $checkTheirTypeOrig; } // infer our field type from their field if (($this->type ?? null) === null) { $this->type = $analysingTheirModel->getField($this->getTheirFieldName($analysingTheirModel))->type; } $this->referenceLink = $this->link; // named differently than in Model\FieldPropertiesTrait $fieldPropsRefl = (new \ReflectionClass(Model\FieldPropertiesTrait::class))->getProperties(); $fieldPropsRefl[] = (new \ReflectionClass(Model\JoinLinkTrait::class))->getProperty('joinName'); $ourModel = $this->getOurModel(); if (!$ourModel->hasField($this->ourField)) { $fieldSeed = []; foreach ($fieldPropsRefl as $fieldPropRefl) { $v = $this->{$fieldPropRefl->getName()}; $vDefault = \PHP_MAJOR_VERSION === 7 ? ($fieldPropRefl->getDeclaringClass()->getDefaultProperties()[$fieldPropRefl->getName()] ?? null) : (null ?? ($fieldPropRefl->hasDefaultValue() ? $fieldPropRefl->getDefaultValue() : null)); // @phpstan-ignore-line for PHP 7.x if ($v !== $vDefault) { $fieldSeed[$fieldPropRefl->getName()] = $v; } } $ourModel->addField($this->ourField, $fieldSeed); } // TODO seeding thru Model\FieldPropertiesTrait is a hack, at least unset these properties for now foreach ($fieldPropsRefl as $fieldPropRefl) { if ($fieldPropRefl->getName() !== 'caption') { // "caption" is also defined in Reference class unset($this->{$fieldPropRefl->getName()}); } } } #[\Override] public function isOneToOne(): bool { return true; } /** * Returns our field or id field. */ protected function referenceOurValue(): Field { // TODO horrible hack to render the field with a table prefix, // find a solution how to wrap the field inside custom Field (without owner?) $ourModelCloned = clone $this->getOurModel(); $ourModelCloned->persistenceData['use_table_prefixes'] = true; return $ourModelCloned->getReference($this->link)->getOurField(); } /** * If our model is loaded, then return their model with respective loaded entity. * * If our model is not loaded, then return their model with condition set. * This can happen in case of deep traversal $model->ref('Many')->ref('one_id'), for example. */ #[\Override] public function ref(Model $ourModelOrEntity, array $defaults = []): Model { $this->assertOurModelOrEntity($ourModelOrEntity); $theirModel = $this->createTheirModel($defaults); if ($ourModelOrEntity->isEntity()) { $this->onHookToTheirModel($theirModel, Model::HOOK_AFTER_SAVE, function (Model $theirEntity) use ($ourModelOrEntity) { $theirValue = $this->theirField !== null ? $theirEntity->get($this->theirField) : $theirEntity->getId(); if (!$this->getOurField()->compare($this->getOurFieldValue($ourModelOrEntity), $theirValue)) { $ourModelOrEntity->save([$this->getOurFieldName() => $theirValue]); } $theirEntity->reload(); }, [], self::HOOK_PRIORITY_EARLY); $theirModel->reloadAfterSave = false; $this->onHookToTheirModel($theirModel, Model::HOOK_AFTER_DELETE, function (Model $theirEntity) use ($ourModelOrEntity) { $ourModelOrEntity->setNull($this->getOurFieldName()); }, [], self::HOOK_PRIORITY_EARLY); $ourValue = $this->getOurFieldValue($ourModelOrEntity); if ($this->getOurFieldName() === $ourModelOrEntity->idField) { $this->assertReferenceValueNotNull($ourValue); $tryLoad = true; } else { $tryLoad = false; } $theirModelOrig = $theirModel; if ($ourValue === null) { $theirModel = null; } else { $theirModel->addCondition($this->getTheirFieldName($theirModel), $ourValue); $theirModel = $tryLoad ? $theirModel->tryLoadOne() : $theirModel->loadOne(); } if ($theirModel === null) { $theirModel = $theirModelOrig->createEntity(); } } return $theirModel; } }