public function save()
{
$objectHash = spl_object_hash($this);
if (array_key_exists($objectHash, self::$saved)) {
return true;
}
self::$saved[$objectHash] = true;
$data = [];
$validation = [];
$one2many = AttributeType::ONE2MANY;
$many2many = AttributeType::MANY2MANY;
/* @var $attr AbstractAttribute */
foreach ($this->getAttributes() as $key => $attr) {
if ($attr->isRequired() && !$attr->hasValue()) {
$ex = new ValidationException(ValidationException::VALIDATION_FAILED);
$ex->addError($key, ValidationException::REQUIRED);
$validation[$key] = $ex;
continue;
}
if (!count($validation) && !$this->isInstanceOf($attr, $one2many) && !$this->isInstanceOf($attr, $many2many)) {
if ($attr->getStoreToDb()) {
$data[$key] = $attr->getDbValue();
}
}
}
// Throw EntityException with invalid attributes
if (count($validation)) {
$attributes = [];
foreach ($validation as $attr => $error) {
foreach ($error as $key => $value) {
$attributes[$key] = $value;
}
}
$ex = new EntityException(EntityException::VALIDATION_FAILED, [count($validation)]);
$ex->setInvalidAttributes($attributes);
throw $ex;
}
/**
* Insert or update
*/
$mongo = $this->entity()->getDatabase();
if (!$this->exists()) {
$data['_id'] = $mongo->isId($data['id']) ? $mongo->id($data['id']) : $mongo->id();
$data['id'] = (string) $data['_id'];
$mongo->insertOne(static::$entityCollection, $data);
$this->id = $data['id'];
} else {
$where = ['_id' => $mongo->id($this->id)];
$mongo->update(static::$entityCollection, $where, ['$set' => $data], ['upsert' => true]);
}
/**
* Now save One2Many values
*/
foreach ($this->getAttributes() as $attr) {
/* @var $attr One2ManyAttribute */
if ($this->isInstanceOf($attr, AttributeType::ONE2MANY) && $attr->isLoaded()) {
foreach ($attr->getValue() as $item) {
$item->getAttribute($attr->getRelatedAttribute())->setValue($this);
$item->save();
}
/**
* The value of one2many attribute must be set to null to trigger data reload on next access.
* This is necessary when we have circular references, and parent record does not get it's many2one ID saved
* until all child referenced objects are saved. Only then can we get proper links between referenced classes.
*/
$attr->setValue(null, true);
}
}
/**
* Now save Many2Many values
*/
foreach ($this->getAttributes() as $attr) {
/* @var $attr Many2ManyAttribute */
if ($this->isInstanceOf($attr, AttributeType::MANY2MANY)) {
$attr->save();
}
}
// Now that this entity is saved, remove it from save log
unset(self::$saved[$objectHash]);
return true;
}