Welcome Guest, Not a member yet? Register   Sign In
How to clean Entity fields before saving
#1

Hello, 
I find the Entity class very useful and I use them a lot, for every database table I have a Model and its Entity class. 
What I find also useful is to get all the POST params from a form and fill the Entity with that data:
PHP Code:
$data $this->request->getPost();

$user = new \App\Entities\User();
$user->fill($data);
$userModel->save($user); 
But here I have some problems that I want to solve. If I have an input field in the form that is not a field of the table as well, it fires an error. For example "Unknown column 'amount_str' in 'field list'".

Yes, he is right, I don't have an amount_str field in my table.

I'd like to "clean" my Entity fields ... or better, ignore all those fields that don't match with the table fields. 
A sort of
PHP Code:
$fields $this->getFieldNames($this->table);

$objectParams array_keys($entityObject);

foreach (
$objectParams as $key => $value
{
 
      
if(!in_array($value$fields))
        unset($entityObject[$value]);
}

return 
$entityObject



Just before saving in the database so I don't get the error. 
What do you think it's a better way to write this once for all? I think it's something to put in the Model. Extending the save method?!
Reply
#2

I found a way that works for me and for my specific project, I have the code in a single point and I get what I want.

I extended the Model class. I call my new model: StdModel.
In the StdModel I have redeclared the save() method in this way:

PHP Code:
public function save($data): bool
 
{

        $fields $this->getFieldNames($this->table);
 
        $objectParams array_keys($data->toArray());
        
        
if(!empty($objectParams))
        {
           foreach ($objectParams as $key => $value
           {  
              
if(!in_array($value$fields))
                 unset($data->$value);
           }
        }
        
        
return parent::save($data);
 } 

Now I can POST every parameter I'd like from the form or frontend, it will always map the table fields before saving, I have tested with insert only but it should work also with updating.
Reply
#3

Great solution! This should be in the framework.
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
/*** NO support in private message - Use the forum! ***/
Reply
#4

Do you have the $allowedFields filled out in the model with only the fields that can be updated? The model should take care of only allowing those fields through and ignoring anything else in the entity.
Reply
#5

As kilishan has already mentioned you should define $allowedFields within you model that's handling the entity.
This performs array_intersect_keys against any given array or entity that your are trying to save/update or insert.

Basically if you have:

PHP Code:
$this->allowedFields = ['user_id']; 

And your incoming _POST array or keys in your Entity look similar to:

PHP Code:
[
    'random' => 'stuff',
    'bad' => 'things',
    'user_id' => 1,
    'get' => 'intersected'


Then only 'user_id' from example above gets passed to database. Now here's the point that I just cannot stress enough:
Always declare $this->validationRules in your Model to make sure that keys that get passed: also get validated against expectations.

On the other hand: Maybe I got your question wrong and you wanted to know how can you Filter out keys that you can store in the $entity object itself?
If so here's a quick example to help anyone who's in need:

PHP Code:
<?php

class MyEntity
{
    public int $user_id 0
    # Add more local keys with default values here ...

    # ...

    # This 'magic' method makes sure every time you set 
    # current entity's local value it must be pre-existing.
    public function __set(string $keymixed $value): void
    
{
        if (property_exists($this$key)) {
            $this->$key $value;
        }
    }

    # If you want to initiate entity with array of keys, ex:
    # new Entity\MyEntity(['user_id' => 8]);
    # then you would love to have a constructor like that:
    public function __construct(array $dataset = [])
    {
        if (! empty($dataset)) {
            foreach ($dataset as $key => $value) {
                # Set each key.
                $this->$key $value;
            }
        }
    }


Have fun!
Reply
#6

(12-19-2021, 08:55 PM)kilishan Wrote: Do you have the $allowedFields filled out in the model with only the fields that can be updated? The model should take care of only allowing those fields through and ignoring anything else in the entity.
You're right, I think I didn't have enough coffee!  Blush
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
/*** NO support in private message - Use the forum! ***/
Reply
#7

(This post was last modified: 12-22-2021, 01:14 AM by Jag81.)

(12-19-2021, 08:55 PM)kilishan Wrote: Do you have the $allowedFields filled out in the model with only the fields that can be updated? The model should take care of only allowing those fields through and ignoring anything else in the entity.

I have the flag $protectFileds=false in the Model.
 
This is a great point, thanks kilishan. Probably it's time to better understand the allowed fields and protectedFileds of the Model. 

I have a table with a lot of fields, some of those fields are filled by the user using a form, others are set by the controller during the process (like some flags or stuff like that).

To get things working I should put every field in $allowedFields. For some tables, this can be a time-wasting, also, I allowed everything every time, which does not sound like a good practice.

Let's say I have a contacts table. The tenant fills the form with: name, email and clicks "save".
I get the request, instantiate the Contact Entity, fill it with the POST requests, then I have to set some following params:
PHP Code:
$entity->highlight=true
$entity
->start_tutorial=true
$entity
->user_id session('user_id')
$entity->tenant_id session('tenant_id'

after an API server-to-server:

PHP Code:
$entity->stripe_customer_id API_RESPONSE
...
... 

and then $model->save($entity);

So, in the model, I should set (at least):
PHP Code:
protected $protectFields    true;
protected 
$allowedFields    = ['name''email''tenant_id''user_id''highlight''start_tutorial''stripe_customer_id' ... all other params I want to save]; 


How do you guys treat this scenario?
Reply
#8

I always have $protectFields = true;. I don't see it as wasting time because a) it only takes a couple of minutes in most cases, and b) you have to specify that at some point. You're either filling out the $allowedFields and passing bulk data in, or you're specifying which fields from the request you want to pass into, ensuring to select only the fields you want.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB