Search code examples
model-view-controllercodeigniter-4

Hiding attributes in model in CodeIgniter 4


I have a CodeIgniter 4 Model.
When I return data from it with find()/findAll(), it returns all the attributes that are present in the model.

$userModel->findAll();

I'd like to hide some of them, for example, I don't want to return the dates (created_at, updated_at).

I tried to create an Entity object and return it, but it also returns everything.

Basically what I'd like to do is to have functionality like in Laravel, where you have a $hidden protected array in the Model.

I've been experimenting with the afterFind callback, but I don't like the solution. Mainly because there is a difference in the $data array when you use find() vs findAll()

protected $afterFind = ['prepareOutput'];

protected function prepareOutput(array $data) {
    return $data;
}

$data['data'] is the actual data when using find()
$data['data'] is an array of arrays when using findAll();
Which kinda makes sense, but then I have to be sure to modify the data according to the method used.

Is there something I am missing in the documentation? Can this be done in a simpler way?


Solution

  • So I came up with this, but I'm still open to better and nicer solutions.

    First I created my own Model and added the logic to hide some attributes:

    <?php
    
    namespace App\Models;
    
    use CodeIgniter\Model;
    
    class MyBaseModel extends Model {
    
        protected $hidden = [];
    
        public function prepareOutput(array $data) {
    
            // if the hidden array is empty, we just return the original dta
            if (sizeof($this->hidden) == 0) return $data;
    
            // if no data was found we return the original data to ensure the right structure
            if (!$data['data']) return $data;
    
            $resultData = [];
    
            // We want the found data to be an array, so we can loop through it.
            // find() and first() return only one data item, not an array
            if (($data['method'] == 'find') || ($data['method'] == 'first')) {
                $data['data'] = [$data['data']];
            }
    
            if ($data['data']) {
                foreach ($data['data'] as $dataItem) {
                    foreach ($this->hidden as $attributeToHide) {
                        // here we hide the unwanted attributes, but we need to check if the return type of the model is an array or an object/entity
                        if (is_array($dataItem)) {
                            unset($dataItem[$attributeToHide]);
                        } else {
                            unset($dataItem->{$attributeToHide});
                        }
                    }
                    array_push($resultData, $dataItem);
                }
            }
    
            // return the right data structure depending on the method used
            if (($data['method'] == 'find') || ($data['method'] == 'first')) {
                return ['data' => $resultData[0]];
            } else {
                return ['data' => $resultData];
            }
    
        }
    
    }
    

    Now I can extend my existing models and fill in which attributes I want to hide:

    <?php namespace App\Models;
        
    use App\Entities\User;
    
    class UserModel extends MyBaseModel {
    
        protected $table = 'users';
        protected $primaryKey = 'id';
    
        protected $returnType = User::class;
        protected $useSoftDeletes = false;
    
        protected $allowedFields = ['email', 'password', 'first_name', 'last_name'];
    
        protected $useTimestamps = true;
        protected $createdField = 'created_at';
        protected $updatedField = 'updated_at';
    
        protected $allowCallbacks = true;
        protected $afterFind = ['prepareOutput'];
        protected $hidden = ['created_at', 'updated_at'];
    
    }