php – Laravel: Tree filter children recursive-ThrowExceptions

Exception or error:

I cannot seem to apply filtering on all children defined in a tree model format with eager loading mechanism

Here is my model definition (works great):

class Section extends Model
{
    [...]
    /**
     * @return HasOne
     */
    public function parent()
    {
        return $this->hasOne(
            self::class,
            'Id',
            'IdParent'
        )->with('parent');
    }

    /**
     * @return HasMany
     */
    public function children()
    {
        return $this->hasMany(
            self::class,
            'IdParent',
            'Id'
        )->with('children');
    }
    [...]
}

Now I want to filter out recursive based on a ‘criteria object’

public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
    /**
     * @var $builder Builder|Section
     */
    $builder = Section::with([
        'children' => function ($query) use ($sectionCriteria) {
            if ($sectionCriteria) {
                foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
                    if ($flagValue) {
                        $query->whereFlag($flagName);  //Custom implementation
                    } else {
                        $query->whereNotFlag($flagName); //Custom implementation
                    }
                }
            }
        }
    ]);

This works bot it is applied to the first level of the tree.

My question would be: Is there a way to pass an object to the children() relation so I can apply filters recursive (which would apply to all levels)?

Something like, let’s say:

P.S: This is not possible since only a callback is accepted as a parameter

public function children($parameters)
{
    return $this->hasMany(
        self::class,
        'IdParent',
        'Id'
    )->with('children'=>$parameters);
}

What I wouldn’t want to use (with respect to SOLID principles):

  1. Make a static class variable which holds criteria
  2. A global variable of any kind
How to solve:

I also tried to retrieve children recursive (and apply filters) but ended up with more queries so Eloquent is preety well optimized sooo…

I used technique #1 (Make a static class variable) though I do not really like it but it works.

Model

/**
 * @var null|SectionCriteria
 */
public static $childrenFilter = NULL;  //This can be whatever you need since it's static


/**
 * @return HasMany
 */
public function children()
{
    return $this->hasMany(
        self::class,
        'IdParent',
        'Id'
    )->with(['children'=>self::searchChild()]);
}

/**
 * @return \Closure
 */
public function searchChild()
{
    return function ($builder) {
        if (Section::$childrenFilter) {
           foreach ($sectionCriteria->getFilterFlags() as $flagName => $flagValue) {
                if ($flagValue) {
                    $query->whereFlag($flagName);  //Custom implementation
                } else {
                    $query->whereNotFlag($flagName); //Custom implementation
                }
            }
        }
    };
}

/**
 * @param SectionCriteria $criteria
 */
public static function setChildSearch(SectionCriteria $criteria)
{
    Section::$childrenFilter = $criteria;
}

/**
 * Remove the search criteria filter
 */
public static function clearChildSearch()
{
    Section::$childrenFilter = NULL;
}

Repository (the actual usage)

/**
 * @param SectionCriteria|NULL $sectionCriteria
 * @return Section[]|Collection
 */
public function getMachines(SectionCriteria $sectionCriteria = NULL)
{
    /**
     * @var $builder Builder|Section
     */
    $builder = Section::with(['children']); //Here I do not need the root elements to be filtered, If needed then use: Section::with(['children'=>Section::searchChild()])
    Section::setChildSearch($sectionCriteria);
    $builder->orderBy('Name');
    $results = $builder->get();
    Section::clearChildSearch();
    return $results;
}

Again…not preety but it gets the job done

New: Another way (will test this out) would be to extend the Builder class

Leave a Reply

Your email address will not be published. Required fields are marked *