Stop duplicating your Eloquent query scopes and constraints in PHP. This package lets you re-use your query scopes and constraints by adding them as a subquery.

Overview

Laravel Eloquent Scope as Select

Latest Version on Packagist run-tests Quality Score Total Downloads Buy us a tree

Stop duplicating your Eloquent query scopes and constraints in PHP. This package lets you re-use your query scopes and constraints by adding them as a subquery.

📺 Want to see this package in action? Join the live stream on December 3 at 14:00 CET: https://youtu.be/0vR8IQSFsfQ

Requirements

  • PHP 7.4+
  • Laravel 7.0 and higher

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.

Related package: Laravel Eloquent Where Not

Support

We proudly support the community by developing Laravel packages and giving them away for free. Keeping track of issues and pull requests takes time, but we're happy to help! If this package saves you time or if you're relying on it professionally, please consider supporting the maintenance and development.

Blogpost

If you want to know more about the background of this package, please read the blogpost: Stop duplicating your Eloquent query scopes and constraints. Re-use them as select statements with a new Laravel package.

Installation

You can install the package via composer:

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

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');
}

Short API description

For a more practical explanation, check out the usage section below.

Add a select using a Closure. Each Post model, published or not, will have an is_published attribute.

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

The example above can be shortened by using a string, where the second argument is the name of the scope:

Post::addScopeAsSelect('is_published', 'published')->get();

You can use an array to call multiple scopes:

Post::addScopeAsSelect('is_popular_and_published', ['popular', 'published'])->get();

Use an associative array to call dynamic scopes:

Post::addScopeAsSelect('is_announcement', ['ofType' => 'announcement'])->get();

If your dynamic scopes require multiple arguments, you can use an associative array:

Post::addScopeAsSelect('is_announcement', ['publishedBetween' => [2010, 2020]])->get();

You can also mix dynamic and non-dynmaic scopes:

Post::addScopeAsSelect('is_published_announcement', [
    'published',
    'ofType' => 'announcement'
])->get();

The method has an optional third argument that flips the result.

Post::addScopeAsSelect('is_not_announcement', ['ofType' => 'announcement'], false)->get();

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.

Solution

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();

Now every Post model will have an is_published boolean attribute.

$posts->each(function (Post $post) {
    $isPublished = $post->is_published;
});

You can add multiple selects as well, for example, to combine both scenarios:

Post::query()
    ->addScopeAsSelect('is_published', function ($query) {
        $query->published();
    })
    ->addScopeAsSelect('is_recent_and_popular', function ($query) {
        $query->publishedInCurrentYear()->has('comments', '>=', 10);
    })
    ->get()
    ->each(function (Post $post) {
        $isPublished = $post->is_published;

        $isRecentAndPopular = $post->is_recent_and_popular;
    });

Shortcuts

Instead of using a Closure, there are some shortcuts you could use (see also: Short API description):

Using a string instead of a Closure:

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

// is the same as:

Post::addScopeAsSelect('is_published', 'published');

Using an array instead of Closure, to support multiple scopes and dynamic scopes:

Post::addScopeAsSelect('is_announcement', function ($query) {
    $query->ofType('announcement');
});

// is the same as:

Post::addScopeAsSelect('is_announcement', ['ofType' => 'announcement']);

You can also flip the result with the optional third parameter (it defaults to true):

$postA = Post::addScopeAsSelect('is_announcement', ['ofType' => 'announcement'])->first();
$postB = Post::addScopeAsSelect('is_not_announcement', ['ofType' => 'announcement'], false)->first();

$this->assertTrue($postA->is_announcement)
$this->assertFalse($postB->is_not_announcement);

Testing

composer test

Changelog

Please see CHANGELOG for more information about what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Other Laravel packages

  • Laravel Analytics Event Tracking: Laravel package to easily send events to Google Analytics.
  • Laravel Blade On Demand: Laravel package to compile Blade templates in memory.
  • Laravel Cross Eloquent Search: Laravel package to search through multiple Eloquent models.
  • Laravel FFMpeg: This package provides an integration with FFmpeg for Laravel. The storage of the files is handled by Laravel's Filesystem.
  • Laravel Form Components: Blade components to rapidly build forms with Tailwind CSS Custom Forms and Bootstrap 4. Supports validation, model binding, default values, translations, includes default vendor styling and fully customizable!
  • Laravel Paddle: Paddle.com API integration for Laravel with support for webhooks/events.
  • Laravel Verify New Email: This package adds support for verifying new email addresses: when a user updates its email address, it won't replace the old one until the new one is verified.
  • Laravel WebDAV: WebDAV driver for Laravel's Filesystem.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Treeware

This package is Treeware. If you use it in production, then we ask that you buy the world a tree to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.

You might also like...
A simple to use query builder for the jQuery QueryBuilder plugin for use with Laravel.
A simple to use query builder for the jQuery QueryBuilder plugin for use with Laravel.

QueryBuilderParser Status Label Status Value Build Insights Code Climate Test Coverage QueryBuilderParser is designed mainly to be used inside Laravel

If you are beginner in WordPress plugin development or if you want to develop your own store product plugin you use this plugin
If you are beginner in WordPress plugin development or if you want to develop your own store product plugin you use this plugin

hirwa-products-plugin If you are beginner in WordPress plugin development or if you want to develop your own store product plugin you use this plugin

A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)

Laravel MongoDB This package adds functionalities to the Eloquent model and Query builder for MongoDB, using the original Laravel API. This library ex

A DynamoDB based Eloquent model and Query builder for Laravel.

Laravel DynamoDB A DynamoDB based Eloquent model and Query builder for Laravel. You can find an example implementation in kitar/simplechat. Motivation

A Laravel package that allows you to use multiple
A Laravel package that allows you to use multiple ".env" files in a precedent manner. Use ".env" files per domain (multi-tentant)!

Laravel Multi ENVs Use multiple .envs files and have a chain of precedence for the environment variables in these different .envs files. Use the .env

 Provides a Eloquent query builder for Laravel or Lumen
Provides a Eloquent query builder for Laravel or Lumen

This package provides an advanced filter for Laravel or Lumen model based on incoming requets.

Laravel basic Functions, eloquent cruds, query filters, constants

Emmanuelpcg laravel-basics Description Package with basic starter features for Laravel. Install If Builder Constants Install composer require emmanuel

A Laravel package making a diagram of your models, relations and the ability to build them with it
A Laravel package making a diagram of your models, relations and the ability to build them with it

Laravel Schematics This package allows you to make multiple diagrams of your Eloquent models and their relations. It will help building them providing

Simple laravel hook for adding meta tags to head for inertia

laravel seo hook for js frameworks simple hook for adding meta tags to head/head for js frameworks inertia:react,vue, etc... in app/Meta.php put M

Comments
  • [WIP] Support for flipping scopes

    [WIP] Support for flipping scopes

    Solution for https://github.com/protonemedia/laravel-eloquent-scope-as-select/issues/3:

    Eloquent model:

    class Post extends Model
    {
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
        public function comments()
        {
            return $this->hasMany(Comment::class);
        }
    
        public function scopeFrontPage($query)
        {
            $query->where('is_public', 1)
                ->where('votes', '>', 100)
                ->has('comments', '>=', 20)            
                ->whereHas('user', fn($user) => $user->isAdmin())
                ->whereYear('published_at', date('Y'));
        }
    }
    

    The whereNot method has the same syntax as the addScopeAsSelect method:

    $nonFrontPagePosts = Post::whereNot('frontPage')->get();
    
    opened by pascalbaljet 1
  • Invert a scope

    Invert a scope

    It would be great to support inverting scopes:

    class Post extends Model
    {
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
        public function comments()
        {
            return $this->hasMany(Comment::class);
        }
    
        public function scopeFrontPage($query)
        {
            $query->where('is_public', 1)
                ->where('votes', '>', 100)
                ->has('comments', '>=', 20)            
                ->whereHas('user', fn($user) => $user->isAdmin())
                ->whereYear('published_at', date('Y'));
        }
    }
    
    Post::query()
        ->whereScopeNot(fn($query) => $query->frontPage())
        ->get();
    

    This would return all posts that are not on the front page 🤠

    opened by pascalbaljet 1
  • Call scopes with a string or array with additional constraints

    Call scopes with a string or array with additional constraints

    Instead of

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

    You can do:

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

    Multiple scopes:

    $posts = Post::addScopeAsSelect('is_popular_and_published', ['popular', 'published'])->get();
    

    And even use dynamic scopes:

    $posts = Post::addScopeAsSelect('is_popular_and_published_this_year', [
        'popular', 
        'publishedInYear' => date('Y')
    ])->get();
    
    $posts = Post::addScopeAsSelect('is_published_in_nineties', [
        'publishedBetweenYears' => [1990, 2000]
    ])->get();
    
    opened by pascalbaljet 0
Releases(1.4.0)
Owner
Protone Media
We are a Dutch software company that develops apps, websites, and cloud platforms. As we're building projects, we gladly contribute to OSS by sharing our work.
Protone Media
A small package for adding UUIDs to Eloquent models.

A small package for adding UUIDs to Eloquent models. Installation You can install the package via composer: composer require ryangjchandler/laravel-uu

Ryan Chandler 40 Jun 5, 2022
This package lets you add uuid as primary key in your laravel applications

laravel-model-uuid A Laravel package to add uuid to models Table of contents Installation Configuration Model Uuid Publishing files / configurations I

salman zafar 10 May 17, 2022
The package lets you generate TypeScript interfaces from your Laravel models.

Laravel TypeScript The package lets you generate TypeScript interfaces from your Laravel models. Introduction Say you have a model which has several p

Boris Lepikhin 296 Dec 24, 2022
🏭This package lets you create factory classes for your Laravel project.

Laravel Factories Reloaded ?? This package generates class-based model factories, which you can use instead of the ones provided by Laravel. Laravel 8

Christoph Rumpel 372 Dec 27, 2022
This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

This Laravel package merges staudenmeir/eloquent-param-limit-fix and staudenmeir/laravel-adjacency-list to allow them being used in the same model.

Jonas Staudenmeir 5 Jan 6, 2023
Laravel Larex lets you translate your whole Laravel application from a single CSV file.

Laravel Larex Translate Laravel Apps from a CSV File Laravel Larex lets you translate your whole Laravel application from a single CSV file. You can i

Luca Patera 68 Dec 12, 2022
Gretel is a Laravel package for adding route-based breadcrumbs to your application.

Gretel Laravel breadcrumbs right out of a fairy tale. Gretel is a Laravel package for adding route-based breadcrumbs to your application. Defining Bre

Galahad 131 Dec 31, 2022
This Laravel Nova tool lets you run artisan and bash commands directly from Nova 4 or higher.

Laravel Nova tool for running Artisan & Shell commands. This Nova tool lets you run artisan and bash commands directly from nova. This is an extended

Artem Stepanenko 17 Dec 15, 2022
A Laravel package for quickly adding .well-known URLs

A Laravel package for quickly adding .well-known URLs well-known is a Laravel package for quickly adding well-known locations (RFC8615) to a Laravel a

Kim Hallberg 2 Sep 13, 2022
In Laravel, we commonly face the problem of adding repetitive filtering code, this package will address this problem.

Filterable In Laravel, we commonly face the problem of adding repetitive filtering code, sorting and search as well this package will address this pro

Zoran Shefot Bogoevski 1 Jun 21, 2022