Search code examples
phplithium

Exclude results from Lithium find


I'd like to exclude results from a call to a Lithium model's find() method. I need to do this for models with both MongoDB and MySQL data sources, but in SQL I mean something like WHERE myfield NOT IN (1,2,3).

I'd like to just be able to pass a not clause in the conditions array like below, but that doesn't appear to be possible.

Item::all(array('conditions' => array('not' => array('myfield' => array(1,2,3))));

So my question is, is this possible in Lithium in a way that I've overlooked? And if not, what would be the most Lithium-ish way to implement it for my models?


Solution

  • Merely filtering for MongoDB can easily be achieved like this:

    Item::all(array('conditions' =>                                                                                                                                                                                
        array('myfield' => array(                                                                                                                                                                                  
            '$nin' => array(1,2,3)                                                                                                                                                                                 
        ))                                                                                                                                                                                                         
    ));                                                                                                                                                                                                            
    

    If this is something you do a lot you could even create a custom finder for it :

    class MyModel extends \lithium\data\Model {                                                                                                                                                                    
        public static function __init()                                                                                                                                                                            
        {                                                                                                                                                                                                          
            parent::__init();                                                                                                                                                                                      
    
            static::finder('notin', function($self, $params, $chain) {                                                                                                                                             
                // Take all array keys that are not option keys
                $array = array_diff_key($params['options'],
                    array_fill_keys(array('conditions', 'fields','order','limit','page'),0));
                // Clean up options leaving only what li3 expects
                $params['options'] = array_diff_key($params['options'], $array);
                $params['options']['conditions'] = array(
                    'myfield' => array(
                        '$nin' => $array
                    )
                );
    
                return $chain->next($self, $params, $chain);                                                                                                                                                       
            });                                                                                                                                                                                                    
        }                                                                                                                                                                                                          
    }                                                                                                                                                                                                              
    

    And call it like this :

    MyModel::notin(array(1,2,3));                                                                                                                                                                                  
    

    In the same manner you could create a custom finder for MySQL sources.

    As you probably can see this creates some issues if you pass something like array('fields'=>$array) as it would overwrite the option. What happens is that ::notin() (finders in general) has a distinct behavior for the (array,null) signature. If that happens it thinks the first array is options and the finder took no arguments. Using notin($array,array()) breaks the previous finder because the first argument ends up in $params['notin'] when the real second argument (options) is passed.

    If you mix data sources on the fly here I would create a custom model that does not inherit \lithium\data\Model and have it delegate
    to the different models and create the conditions based on the end models data source.

    class MyFacadeModel {                                                                                                                                                                                          
        public static function byNotIn($conditions, $source) {                                                                                                                                                     
            return ($source == "mongodb")                                                                                                                                                                          
                ? $source::find( $rewrittenConditions)                                                                                                                                                             
                : $source::find( $rewrittenConditionsForMysql );                                                                                                                                                   
        }                                                                                                                                                                                                          
    }
    

    (Code might be slightly incorrect as its mostly taken from the top of my head)