2

I have a scope which is acting in a limiting fashion depending on a user role in a way that you can forward a set of rules to the scope limiting the final output from the DB.

A really simplified role limit example:

{
    "first_name": "=foo%"
}

will only return records whose first_name begins with foo%, which effectively means that I have forbidden the user with that role to see any records which do not begin with foo%.

The issue with this approach is that if someone has no limits imposed, he will see everything. I am aware that this is a valid assumption, but I would like to forbid everything if there are no limits imposed, so that if new role is created, it has no rights whatsoever.

Currently I am throwing an exception for this case:

public function apply(Builder $builder, Model $model)
{
    $input = $this->getAuthValues(get_class($model));

    if (count($input) < 1) {
        throw new \Exception('No rights for you');
    }

    $jsonQuery = new JsonQuery($builder, $input); <-- class responsible for assembling the queries based on the input
    $jsonQuery->search();
}

But this of course is no exception. I would like empty array to be returned in that case.

Can I use some method to gracefully exit the scope together with an empty result, without actually executing the query afterwards?

Norgul
  • 4,613
  • 13
  • 61
  • 144

2 Answers2

1

Since scopes are usually chained (called fluently), and the scope isn't directly responsible for executing the query, I don't think you can "gracefully exit" since the next call will still be expecting a Builder object to work on.

One possible solution (Not simple), would be to create a class that extends the Builder class, and override all of the methods responsible for actually fetching results from the DB. I am not sure all of the methods you would need to override for this to work without error in all cases. You probably would also want to handle some of the insertion and update cases in the AbortedBuilder as well.

class AbortedBuilder extends Builder
{
    ...

    /**
     * Run the query as a "select" statement against the connection.
     *
     * @return array
     */
    protected function runSelect()
    {
        return [];
    }
    
    ...

    /**
     * Run a pagination count query.
     *
     * @param  array  $columns
     * @return array
     */
    protected function runPaginationCountQuery($columns = ['*'])
    {
        return [];        
    }

    ...

    /**
     * Insert a new record and get the value of the primary key.
     *
     * @param  array  $values
     * @param  string|null  $sequence
     * @return int
     */
    public function insertGetId(array $values, $sequence = null)
    {
        throw new \Exception('You are trying to insert using an aborted query!');
    }

    ...
}

Then in your scope you could do:

public function apply(Builder $builder, Model $model)
{
    $input = $this->getAuthValues(get_class($model));

    if (count($input) < 1) {
        $builder = new AbortedBuilder();
    } else {
        $jsonQuery = new JsonQuery($builder, $input);
        $jsonQuery->search();
}
Kurt Friars
  • 3,625
  • 2
  • 16
  • 29
  • 1
    Thanks Kurt. While thinking a bit more, I actually came to a simpler solution. I will post it below – Norgul Jul 21 '20 at 09:33
1

I came to a solution to make an impossible query which would return 0 results.

public function apply(Builder $builder, Model $model)
{
    $input = $this->getAuthValues(get_class($model));

    if (count($input) < 1) {
        $builder->whereRaw('1 = 0');
        return;
    }

    $jsonQuery = new JsonQuery($builder, $input);
    $jsonQuery->search();
}
Norgul
  • 4,613
  • 13
  • 61
  • 144