From b7a8a8a3ca6fc9fd6262fb41df04e54a00ce41e7 Mon Sep 17 00:00:00 2001
From: Michael Krecek <michael@krecek.net>
Date: Thu, 25 Aug 2022 16:29:10 +0200
Subject: [PATCH 1/3] Add Model::saveWithoutScope

---
 src/Model.php | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/src/Model.php b/src/Model.php
index 755cfd4ad..ad0ee445c 100644
--- a/src/Model.php
+++ b/src/Model.php
@@ -1384,6 +1384,34 @@ public function saveAndUnload(array $data = [])
         return $this;
     }
 
+    /**
+     * Store the data into database, but will never attempt to
+     * reload the data and permits that entity leaves scope after save.
+     * Additionally, any data will be unloaded. removeConditionFields will
+     * eliminate all conditions on those fields, set null to ignore all conditions.
+     *
+     * @return $this
+     */
+    public function saveWithoutScope(array $data = [], array $removedConditionFields = null)
+    {
+        $scopeElementsOrig = $this->getModel()->scope()->elements;
+        try {
+            foreach ($this->getModel()->scope()->elements as $k => $v) {
+                if ($v instanceof Model\Scope\Condition && (!$removedConditionFields || in_array($v->key, $removedConditionFields))) {
+                    unset($this->getModel()->scope()->elements[$k]);
+                }
+            }
+
+            $this->saveAndUnload($data);
+
+        } finally {
+            $this->getModel()->scope()->elements = $scopeElementsOrig;
+        }
+
+
+        return $this;
+    }
+
     /**
      * Create new model from the same base class as $this.
      *

From a45abe8247e628aa0442107d51913b6a424a73eb Mon Sep 17 00:00:00 2001
From: Michael Krecek <michael@krecek.net>
Date: Thu, 25 Aug 2022 16:52:20 +0200
Subject: [PATCH 2/3] Optimized performance

---
 src/Model.php | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/Model.php b/src/Model.php
index ad0ee445c..a8e9b5a2c 100644
--- a/src/Model.php
+++ b/src/Model.php
@@ -1396,11 +1396,13 @@ public function saveWithoutScope(array $data = [], array $removedConditionFields
     {
         $scopeElementsOrig = $this->getModel()->scope()->elements;
         try {
-            foreach ($this->getModel()->scope()->elements as $k => $v) {
-                if ($v instanceof Model\Scope\Condition && (!$removedConditionFields || in_array($v->key, $removedConditionFields))) {
-                    unset($this->getModel()->scope()->elements[$k]);
+            if ($removedConditionFields) {
+                foreach ($this->getModel()->scope()->elements as $k => $v) {
+                    if ($v instanceof Model\Scope\Condition && (!$removedConditionFields || in_array($v->key, $removedConditionFields))) {
+                        unset($this->getModel()->scope()->elements[$k]);
+                    }
                 }
-            }
+            } else { $this->getModel()->scope()->elements = array(); }
 
             $this->saveAndUnload($data);
 

From 6827c6c5498bc57f7ba7edb22054397f42869f02 Mon Sep 17 00:00:00 2001
From: Michael Krecek <michael@krecek.net>
Date: Thu, 25 Aug 2022 20:10:09 +0200
Subject: [PATCH 3/3] First steps to introduce weak conditions, WIP, help
 needed on storing weak flag

---
 src/Model.php                 | 63 +++++++++++++++++++++++++++++++++--
 src/Model/Scope.php           |  2 +-
 src/Model/Scope/Condition.php |  3 ++
 3 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/src/Model.php b/src/Model.php
index a8e9b5a2c..9f842d6a2 100644
--- a/src/Model.php
+++ b/src/Model.php
@@ -1002,6 +1002,30 @@ public function addCondition($field, $operator = null, $value = null)
         return $this;
     }
 
+    /**
+     * Same as addCondition but condition will be dropped on save event and not be enforced.
+     * Therefore, it is valid to save an entity where the saved entity will not comply to the
+     * weak condition anymore.
+     *
+     * Example:
+     *
+     * $messsageModel->addWeakCondition('unread', true);
+     * $messageModel->save(['unread' => false]);
+     *
+     * @param mixed $field
+     * @param mixed $operator
+     * @param mixed $value
+     *
+     * @return $this
+     */
+    public function addWeakCondition($field, $operator = null, $value = null)
+    {
+
+        $this->scope()->addCondition(...func_get_args(), true);
+
+        return $this;
+    }
+
     /**
      * Adds WITH/CTE model.
      *
@@ -1340,6 +1364,21 @@ public function reload()
         return $this;
     }
 
+    /**
+     * Try to reload model by taking its current ID, otherwise return null.
+     *
+     * @return $this
+     */
+    public function tryReload()
+    {
+        $id = $this->getId();
+        $this->unload();
+
+        $res = $this->_load(true, true, $id);
+
+        return $this;
+    }
+
     /**
      * Keeps the model data, but wipes out the ID so
      * when you save it next time, it ends up as a new
@@ -1495,10 +1534,30 @@ public function tryLoadBy(string $fieldName, $value)
         return $this->_loadBy(true, $fieldName, $value);
     }
 
+    protected function invokeCallbackWithoutWeakConditions(Model $model, \Closure $callback)
+    {
+        $scopeElementsOrig = $model->scope()->elements;
+        try {
+            foreach ($model->scope()->elements as $k => $v) {
+                if ($v instanceof Model\Scope\Condition && $v->weak) {
+                    unset($model->scope()->elements[$k]);
+                }
+            }
+
+            return $callback();
+        } finally {
+            $model->scope()->elements = $scopeElementsOrig;
+        }
+    }
+
     protected function validateEntityScope(): void
     {
         if (!$this->getModel()->scope()->isEmpty()) {
-            $this->getPersistence()->load($this->getModel(), $this->getId());
+
+            $this->invokeCallbackWithoutWeakConditions($entity->getModel(), function () use ($entity): void {
+                $this->getPersistence()->load($this->getModel(), $this->getId());
+            });
+
         }
     }
 
@@ -1592,7 +1651,7 @@ public function save(array $data = [])
             if ($this->idField && $this->reloadAfterSave) {
                 $d = $dirtyRef;
                 $dirtyRef = [];
-                $this->reload();
+                $this->tryReload();
                 $this->dirtyAfterReload = $dirtyRef;
                 $dirtyRef = $d;
             }
diff --git a/src/Model/Scope.php b/src/Model/Scope.php
index 48256b330..c7dae28c1 100644
--- a/src/Model/Scope.php
+++ b/src/Model/Scope.php
@@ -74,7 +74,7 @@ public function __clone()
      *
      * @return $this
      */
-    public function addCondition($field, $operator = null, $value = null)
+    public function addCondition($field, $operator = null, $value = null, $weak = false)
     {
         if (func_num_args() === 1 && $field instanceof Scope\AbstractScope) {
             $condition = $field;
diff --git a/src/Model/Scope/Condition.php b/src/Model/Scope/Condition.php
index 25e70a55a..41289d598 100644
--- a/src/Model/Scope/Condition.php
+++ b/src/Model/Scope/Condition.php
@@ -25,6 +25,9 @@ class Condition extends AbstractScope
     /** @var mixed */
     public $value;
 
+    /** @var boolean */
+    public $weak = false;
+
     public const OPERATOR_EQUALS = '=';
     public const OPERATOR_DOESNOT_EQUAL = '!=';
     public const OPERATOR_GREATER = '>';