-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: disallow Model::update()
without WHERE clause
#6883
Conversation
I would prefer to have something less invasive. Maybe something like That could serve both methods: $model->set($data)->allowEmptyWhere()->update();
$model->allowEmptyWhere()->delete(); |
What do you mean? I would like to deprecate the usage |
Yes, that's true. But there is a big difference, at least from my point of view. We all have our habits. If I already have If instead of this, we will use Plus, as I mentioned, this also can be used with soft deletes. I don't think there is any straightforward way right now to soft delete all the records.
I get your point, but again - with I'm not pushing to do it my way. I got the idea after reading the code from this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels very heavy-handed. I agree this will probably prevent more mistakes than actual existing use cases, but it could really break current projects.
Alternatives: we could add it to our Features Config and encourage people to turn it on in the release notes / upgrade guides. We could trigger & log deprecations or warnings with the intent to make the hard change in a future version (4.4? 5.0?). Other ideas?
I don't quite follow what the problem is. Can you give a code example of a problematic situation? |
You can follow the thread on the forum: https://forum.codeigniter.com/showthread.php?tid=84833 - Kenjis gives some examples there. |
I'm just looking into this problem now and still don't yet get everything that is going on here but I can say that using the new updateBatch() method would probably make more sense for model. It works in a completely different manner and would probably not have issues like this associated with it. updateBatch() performs an update based on each row of data and not based on a WHERE. I'll look some more at this but those are my initial thoughts. |
@sclubricants |
If when calling update() on model ..if model then calls $builder->onContraint(id)->updateBatch($dat) then it would throw an error if id is not present. If id is present but null then the data would match no rows and the rows with null ids wouldn't update anything. Using model assumes there is a constraint or pk. So mapping update to updateBatch() should be feasible. If your updating records in any other manner you really shouldn't use model. Any models without an id/pk should only be able to insert. I need to dig in this a bit more though so I understand the problem better. |
The method says it only updates a single record. Unfortunately it does not always deliver that promise. As an alternative: protected function doUpdate($allOrId = false, $data = null): bool
{
$escape = $this->escape;
$this->escape = [];
$builder = $this->builder();
if ($allOrId !== true) {
$builder = $builder->whereIn($this->table . '.' . $this->primaryKey, $allOrId ?? '');
}
// Must use the set() method to ensure to set the correct escape flag
foreach ($data as $key => $val) {
$builder->set($key, $val, $escape[$key] ?? null);
}
return $builder->update();
} This is still a breaking change but only a small change would be needed. Pass true instead of null to update all |
I'd rather see something like this: public function update($data = null, $alternativeConstraint = null): bool
{
}
protected function doUpdate($data = null, $alternativeConstraint = null): bool
{
$escape = $this->escape;
$this->escape = [];
$builder = $this->builder()->setData($data, $escape)->onConstraint($alternativeConstraint ?? $this->primaryKey);
return $builder->updateBatch();
} |
Maybe for 4.4 I can go though model and refactor some things to be more inline with builder. |
Yes, the comment say so: |
I proposed this breaking change because I think it is a rare use case where all records are updated. Adding Feature Config is fine, but I prefer the default is to disallow updates all. |
I don't see why we need updateAll(). It seems you should use builder for thos types of operations. For that matter make model method that returns an instance of builder with table set. $model->builder()->update() |
However, if many people say that |
Let's make sure @lonnieezell @paulbalandan and @samsonasik have seen this and had a chance to weigh in. |
4ce9c8c
to
d263a65
Compare
Rebased. |
system/BaseModel.php
Outdated
@@ -1037,6 +1041,10 @@ public function updateBatch(?array $set = null, ?string $index = null, int $batc | |||
*/ | |||
public function delete($id = null, bool $purge = false) | |||
{ | |||
if (is_bool($id)) { | |||
throw new InvalidArgumentException('$id should not be boolean.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should likewise be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed.
@@ -899,6 +899,10 @@ public function insertBatch(?array $set = null, ?bool $escape = null, int $batch | |||
*/ | |||
public function update($id = null, $data = null): bool | |||
{ | |||
if (is_bool($id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what instances would this be a bool?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an attacker passes a bool and the dev forgets to validate the id value.
This also should be treated as a BC change - a new reserved method name. |
At least, @sclubricants and @iRedds say that @michalsn If it is a breaking change, adding any new method will be a breaking change? |
86db3ca
to
d087884
Compare
Rebased. Okay, I will remove
Yes. I think it is the way to go. |
d087884
to
c903f86
Compare
Removed updateAll() and added docs. |
That may indeed be true. |
407c78d
to
87a997d
Compare
Updated the existing note in docs. 87a997d |
https://github.com/codeigniter4/CodeIgniter4/actions/runs/3655630049/jobs/6177588634 |
https://github.com/codeigniter4/CodeIgniter4/actions/runs/3655902799/jobs/6177741790 |
To fix both psalm and phpstan errors
Ah the old static analysis standoff! 😂 Yes I'm thinking in the same direction as @sclubricants... The intent of Model is to work with identifiable entities (the concept, not the type). I know we aren't officially a repository pattern, but I think the concept stands - and anonymously updating persisted sets of data is not conceptually sound. |
Description
It is not necessary that the
update()
method be able to update all records without WHERE clause. Updating all records is a risky operation and should not be mixed with update method with id(s).The current API is not good design because it is easily to create a vulnerability that allows all records to be updated if you accidentally forget to validate the
$id
. See https://forum.codeigniter.com/showthread.php?tid=84833Model::update()
without WHERE clause (throws Exception).$id
as bool forupdate()
/delete()
(throws Exception).addModel::updateAll($data)
for UPDATE without WHERE clause.Model::detete()
. DELETE without WHERE clause is already not allowed.Checklist: