Laravel.io
class ProductFilter
{
    private $requestData;

    /**
     * ProductFilter constructor.
     * @param $requestData
     */
    public function __construct($requestData)
    {
        $this->requestData = $requestData;
    }

    /**
     * Returns a model from container
     * @return mixed
     */
    private function model()
    {
        return app(Product::class);
    }

    public function prepareCatalogFilterQuery()
    {
        $query = $this->model()->newQuery();

        if(!empty($this->requestData['sort']) && !($this->requestData['sort_order'])) {
            $query->orderBy($this->requestData['sort'], $this->requestData['sort_order']);
        }

        if(!empty($this->price_min) && intval($this->price_min) > 0 && !empty($this->price_max) && intval($this->price_max)) {
            $query->whereBetween('price', [$this->price_min, $this->price_max]);
        }

        if(!empty($this->requestData['category']) && is_array($this->requestData['category'])) {
            $query->with(['categories' => function($subQuery) {
                $subQuery->whereIn('category_id', '=', $this->requestData['category']);
            }]);
        }
        
        return $query;
    }
}

Please note that all pasted data is publicly available.