CodeIgniter Forums

Full Version: Is a beforeFind on a Model a bad idea?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Reworking one project into a pure front/back-end solution with a REST API in CI4 I have been thinking about a way to implement searching and paging of the data returned by find. I have currently a working solution by creating a clone of Model::findAll() and handling paging there. Working, but not the nicest maybe.
Thinking about other possibilities, I wonder why there isn't a beforeFind event where I can get access to the QueryBuilder object to allow manipulation of the find parameters.
What am I missing?

/Mattias
This was actually one of the things that bothered me when I started using ci4. The base model I used to use back in ci3 also had callbacks and it had before and after find. Don't really know why ci4 have all the callbacks built in except this one.
I think I sort of have worked around this. I created a new BaseModel containing the logic for certain things:


PHP Code:
<?php


namespace App\Models;


use 
App\Classes\JsonStoreResponse;
use 
Config\Services;

class 
BaseModel extends \CodeIgniter\Model
{

    protected $useTimestamps true;
    protected $useSoftDeletes false;

    /**
    * Handle pagination with Ext.data.virtual.Store
    * 
    * @return JsonStoreResponse
    */
    public function getVirtualStoreData()
    {
        $request Services::request();

        $start $request->getGetPost('start');
        $limit $request->getGetPost('limit');

        $builder $this->builder();
        $builder->limit($limit$start);

        $sql str_replace('SELECT''SELECT SQL_CALC_FOUND_ROWS'$builder->getCompiledSelect());
        echo $sql;
        $query $this->db->query($sql);
        $row $query->getResult($this->tempReturnType);
        $query->freeResult();

        $query $this->db->query('SELECT FOUND_ROWS() AS `total`');
        $res $query->getRowObject();

        $result = new JsonStoreResponse(true$res->total);
        $result->setData($row);

        return $result;
    }

    /**
    * Check the item_version of the new data and make sure the data is in sync between
    * front- and back-end before updating.
    *
    * @param $id
    * @param $new_data
    *
    * @return bool
    * @throws \ReflectionException
    */
    public function validateVersionAndUpdate($id$new_data)
    {
        $record $this->find($id);

        // Ext JS increments the item_version by one when saving.
        // item_version - 1 must match what we have in database, otherwise another edit
        // has been made and we bail out.
        if ((int)$new_data->item_version !== (int)$record->item_version) {
            return false;
        }

        $this->update($id$new_data);
        return true;
    }



All my models then extend from this BaseModel and get access to common functions without using events.
Working so far...
(06-29-2020, 10:29 PM)tgix Wrote: [ -> ]What am I missing?

/Mattias

It is possible to define custom events. The (static) class method Events::on() allows you to register an event using any name you like.

File: /app/Config/Events.php

PHP Code:
Events::on('before_findAll', [$modelInstance'modelMethod']);  // Call modelMethod on an existing Model instance 


At the appropriate logical spot in your code, something like this

PHP Code:
\CodeIgniter\Events\Events::trigger('before_findAll'$model'modelMethod'); 

You can pass other arguments in trigger if needed.

Events Docs
I never found a consistent use for a beforeFind event, honestly If people feel it's useful then feel free to submit a PR.

For things like searching and filtering for an index page you can always chain methods together. Something like:

In controller:
PHP Code:
$users $userModel->filter($this->request->getPost())->paginate(20); 

and the new method in the model might be something like:

PHP Code:
public function filter($params
{
    if(isset(
$params['search'] && ! empty($params['search'])) {
        
$this->like('name'$params['search'].'%');
    }
    if(isset(
$params['city_id'] && ! empty($params['city_id'])) {
        
$this->like('city_id'$params['city_id'].'%');
    }

    
// Sorting
    
$sortBy = isset($params['sort']) && trim($params['sort'] !== '')
        ? 
$params['sort']
        : 
'first_name';
    
$sortDir = isset($params['sort_dir']) && in_array($params['sort_dir'], ['asc''desc'])
        ? 
$params['sort_dir']
        : 
'asc';
    
$this->orderBy($sortBy$sortDir);

    return 
$this;

(06-30-2020, 12:02 PM)kilishan Wrote: [ -> ]For things like searching and filtering for an index page you can always chain methods together.

Thanks. By creating the BaseModel and then extending all my models from that base I think the code is pretty clean and contained in the Model to offload the Controllers.
Doing a project into a front-end / backend solution with the REST API in CI4 I've been thinking of a way to optimize the availability and paging of data returned by search.We are here.  I have a current working solution by creating a clone of Model :: FindAll () and maintaining paging there. Seriously, but not the best either.