Blog Detail

27

Jan
 Reuse Query Scopes & Constraints as a Subquery in Laravel cover image

arrow_back Reuse Query Scopes & Constraints as a Subquery in Laravel

Protone Media comes up with a package called Laravel Eloquent Scope as Select. This package lets you re-use your query scopes and constraints by adding them as a subquery. So you can stop duplicating your Eloquent query scopes and constraints in PHP.

Requirements

  • PHP 7.4+
  • Laravel 8.0
  • This package is tested with GitHub Actions using MySQL 5.7, PostgreSQL 10.8, and SQLite.

Features

  • Add a subquery based on a query scope.
  • Add a subquery using a Closure.
  • Shortcuts for calling scopes by using a string or array.
  • Support for more than one subquery.
  • Support for flipping the result.
  • Zero third-party dependencies.

Installation

You can install this package via composer by running this command:

composer require protonemedia/laravel-eloquent-scope-as-select

Next, you’ve to add the macro to the query builder, for example, in your AppServiceProvider. By default, the name of the macro is addScopeAsSelect, but you can customize it with the first parameter of the addMacro method.

use ProtoneMedia\LaravelEloquentScopeAsSelect\ScopeAsSelect;

public function boot()
{
    ScopeAsSelect::addMacro();

    // or use a custom method name:
    ScopeAsSelect::addMacro('withScopeAsSubQuery');
}

Usage

Imagine you have a Post Eloquent model with a query scope.

class Post extends Model
{
    public function scopePublished($query)
    {
        return $query->whereNotNull('published_at');
    }
}

Now you can fetch all published posts by calling the scope method on the query:

$allPublishedPosts = Post::published()->get();

But what if you want to fetch all posts and then check if the post is published? This scope is quite simple, so you can easily mimic the scope’s outcome by checking the published_at attribute:

Post::get()->each(function (Post $post) {
    $isPublished = !is_null($post->published_at);
});

This is harder to achieve when scopes get more complicated or when you chain various scopes. Let’s add a relationship and another scope to the Post model:

class Post extends Model
{
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function scopePublished($query)
    {
        return $query->whereNotNull('published_at');
    }

    public function scopePublishedInCurrentYear($query)
    {
        return $query->whereYear('published_at', date('Y'));
    }
}

Using Eloquent, we can fetch all posts from this year with at least ten comments.

$recentPopularPosts = Post::query()
    ->publishedInCurrentYear()
    ->has('comments', '>=', 10)
    ->get();

Great! Now we want to fetch all posts again, and then check if the post was published this year and has at least ten comments.

Post::get()->each(function (Post $post) {
    $isRecentAndPopular = $post->comments()->count() >= 10
        && optional($post->published_at)->isCurrentYear();
});

Well, you get the idea. This is bound to get messy and you’re duplicating logic as well.

Using the power of this package, you can re-use your scopes when fetching data. The first example (published scope) can be narrowed down to:

$posts = Post::addScopeAsSelect('is_published', function ($query) {
    $query->published();
})->get();

With short closures, a feature which was introduced in PHP 7.4, this can be even shorter:

$posts = Post::addScopeAsSelect('is_published', fn ($query) => $query->published())->get();

This package has a lot more details with code examples, If you want to get the documentation and source code you can visit Github.

Published at : 27-01-2022

Author : Rizwan Aslam
AUTHOR
Rizwan Aslam

I am a highly results-driven professional with 12+ years of collective experience in the grounds of web application development especially in laravel, native android application development in java, and desktop application development in the dot net framework. Now managing a team of expert developers at Codebrisk.

Launch your project

Launch project