CodeIgniter Forums

Full Version: Entity - allowedFields add functionality
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
As per https://forum.codeigniter.com/forum-29.html
I thought by adding $allowedFields to an entity that an Entity would behave similar to a Model.
(Indeed I thought that defining $attributes was the same thing as defining $allowedFields in the context of an Entity.)
  • I think that $allowedFields functionality for an entity would be useful. This allows Entities to be more useful in their own right - helpful to standardise inputs to an expected format for interfaces aside from dbs.

Indeed I think from a design perspective that when defining an Entity as per my understanding:

Quote:"At its core, an Entity class is simply a class that represents a single database row." 

Therefore when we are defining fields using $attributes in an Entity shouldn't we effectively be setting the $allowedFields of that database row? 

I think a good implementation would be to have $eligibleFields for Entities and $allowedFields for Models where if $allowedFields is not blank that it takes precedence.
(Typically where an Entity is defined as the $returnType of a Model then that is the place for fields to defined rather than at the model level.)
Whats are your thoughts?
(The $datamap functionality would also be very useful at the entity level so that it can be used standalone from model.)
EDIT: ugh while playing further I set up the entity in error as:
Code:
use CodeIgniter\Entity;

Instead of:
Code:
use CodeIgniter\Entity\Entity;
hence the brain fart.

I get that model functionality will need to work whether or not an entity is used ie. Model needs to retain functionality to define and transform output.

However where an entity is defined then shouldn't this be where "row" definitions are provided???
When an entity is present then: Model is used for transformation / Entity is used for definition?
Entities being reusable across models.

---ok enough whining to myself... this is how I extended the entity class to have similar functionality to allowedFields in Models...

To utilise this in the Entity setup:
PHP Code:
<?php
namespace App\Entities;

class 
JSOutput extends EntityExt

protected 
$attributes = [ ... as you will ...]
protected 
$expectedFields = [    'Originated',    'Tab'    'Price',    'MapLongitude',    'MapLatitude',    'Zoom',];   
protected $datamap = [ ... as you will ...]

From a controller output will be constrained to expectedFields:
PHP Code:
$jsinput = new \App\Entities\JSOutput();  
$jsinput
->fill($currentRequest);
print_r($jsinput->toArray()); 

Added a class EntityExt as an extension of Entity as follows:
Code:
<?php

namespace App\Entities;

use CodeIgniter\Entity\Entity;

class EntityExt extends Entity
{

/*
*      Adds "allowedField" functionality as available in a Model to an Entity 
*
*      There is already allowedField functionality already in Model
*      -this acts to filter output from the model (resulting in interaction with db) to be only of previously allowed fields
*
*    This is similar functionality in Entity
*    - Uses "expectedField" in the entity to differentiate from "allowedField" functionality in the model
*    - extra fields can be added to the entity (eg with the bulk fill) however only previously defined expected fields will be present in array generated output
*    - all expectedFields will be present in array generated output - if the entity has not been filled with a corresponding value a NULL will be populated
*    - be aware that expectedFields use the name of the field prior to it being involved in a Datamap (if applicable)

// Present in BaseModel
    /**
    * Whether we should limit fields in inserts
    * and updates to those available in $expectedFields or not.
    *
    * @var boolean
    */
    protected $protectFields = true;

// Renamed from setAllowedFields in BaseModel   
    /**
    * It could be used when you have to change default or override current expected fields.
    *
    * @param array $expectedFields Array with names of fields
    *
    * @return $this
    */
    public function setExpectedFields(array $expectedFields)
    {
            $this->expectedFields = $expectedFields;

            return $this;
    } 
   

//  This is a modified doProtectFields function from the BaseModel renamed to deExpectedFields

    /**
    * Ensures that only the fields that are expected to be updated
    * are in the data array.
    *
    * Used by toArray() to protect against mass assignment
    * vulnerabilities.
    *
    * @param array $data Data
    *
    * @return array
    *   
    */
     protected function doExpectedFields(array $data): array
    {

            if (! $this->protectFields)
            {
                    return $data;
            }

            if (!empty($this->expectedFields))
            {    
                foreach ($this->expectedFields as $key)
                {
                    (array_key_exists($key, $data)) ? $return[$key]=$data[$key] : $return[$key]=NULL;                        
                }          
                return $return;
            }            
            
            return $data;
    }    
 
/*
*  The entity toArray function needs a single line added to call the doExpectedField check above
*    ie ADD    $this->attributes = $this->doExpectedFields($this->attributes);
   
    /**
* General method that will return all public and protected values
* of this entity as an array. All values are accessed through the
* __get() magic method so will have any casts, etc applied to them.
*
* @param boolean $onlyChanged If true, only return values that have changed since object creation
* @param boolean $cast        If true, properties will be casted.
* @param boolean $recursive  If true, inner entities will be casted as array as well.
*
* @return array
*/
public function toArray(bool $onlyChanged = false, bool $cast = true, bool $recursive = false): array
{           
$this->_cast = $cast;
               
//ADDED Line BELOW               
                $this->attributes = $this->doExpectedFields($this->attributes);
//ADDED Line ABOVE
               
                $keys = array_filter(array_keys($this->attributes), function ($key) {
return strpos($key, '_') !== 0;
});

if (is_array($this->datamap))
{
$keys = array_unique(
array_merge(array_diff($keys, $this->datamap), array_keys($this->datamap))
);
}

$return = [];

// Loop over the properties, to allow magic methods to do their thing.
foreach ($keys as $key)
{
if ($onlyChanged && ! $this->hasChanged($key))
{
continue;
}

$return[$key] = $this->__get($key);

if ($recursive)
{
if ($return[$key] instanceof Entity)
{
$return[$key] = $return[$key]->toArray($onlyChanged, $cast, $recursive);
}
elseif (is_callable([$return[$key], 'toArray']))
{
$return[$key] = $return[$key]->toArray();
}
}
}

$this->_cast = true;

return $return;
}             
   
}
Hope that helps someone else.