master 847096fac908 cached
60 files
90.0 KB
23.8k tokens
183 symbols
1 requests
Download .txt
Repository: fico7489/laravel-eloquent-join
Branch: master
Commit: 847096fac908
Files: 60
Total size: 90.0 KB

Directory structure:
gitextract_4l8jykqq/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .php_cs
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│   ├── EloquentJoinBuilder.php
│   ├── Exceptions/
│   │   ├── InvalidAggregateMethod.php
│   │   ├── InvalidDirection.php
│   │   ├── InvalidRelation.php
│   │   ├── InvalidRelationClause.php
│   │   ├── InvalidRelationGlobalScope.php
│   │   └── InvalidRelationWhere.php
│   ├── Relations/
│   │   ├── BelongsToJoin.php
│   │   ├── HasManyJoin.php
│   │   └── HasOneJoin.php
│   └── Traits/
│       ├── EloquentJoin.php
│       ├── ExtendRelationsTrait.php
│       └── JoinRelationTrait.php
└── tests/
    ├── Models/
    │   ├── BaseModel.php
    │   ├── City.php
    │   ├── Integration.php
    │   ├── Key/
    │   │   ├── Location.php
    │   │   ├── Order.php
    │   │   └── Seller.php
    │   ├── Location.php
    │   ├── LocationAddress.php
    │   ├── LocationWithGlobalScope.php
    │   ├── Order.php
    │   ├── OrderItem.php
    │   ├── Seller.php
    │   ├── State.php
    │   ├── User.php
    │   └── ZipCode.php
    ├── Scope/
    │   └── TestExceptionScope.php
    ├── ServiceProvider.php
    ├── TestCase.php
    ├── Tests/
    │   ├── AggregateJoinTest.php
    │   ├── AppendRelationsCountTest.php
    │   ├── Clauses/
    │   │   ├── JoinRelationsTest.php
    │   │   ├── OrWhereInTest.php
    │   │   ├── OrWhereNotInTest.php
    │   │   ├── OrWhereTest.php
    │   │   ├── OrderByTest.php
    │   │   ├── WhereInTest.php
    │   │   ├── WhereNotInTest.php
    │   │   └── WhereTest.php
    │   ├── ClosureOnRelationTest.php
    │   ├── ClosureTest.php
    │   ├── ExceptionTest.php
    │   ├── JoinTypeTest.php
    │   ├── KeysOwnerTest.php
    │   ├── KeysTest.php
    │   ├── OptionsTest.php
    │   ├── Relations/
    │   │   ├── BelongsToTest.php
    │   │   ├── HasManyTest.php
    │   │   └── HasOneTest.php
    │   └── SoftDeleteTest.php
    └── database/
        └── migrations/
            └── 2017_11_04_163552_create_database.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on:
  pull_request:
  push:
    branches:
      - master

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php_version: [7.4, 8.2]
        laravel_version: [8.*, 10.*, 11.*]

    steps:
      - name: Checkout commit
        uses: actions/checkout@v2

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php_version }}

      - name: Validate composer.json
        run: composer validate
      
      - name: Run composer install
        run: composer install --no-interaction --no-suggest
        
      - name: Run find-and-replace to replace * with 0
        uses: mad9000/actions-find-and-replace-string@1
        id: laravel_version_cleaned
        with:
          source: ${{ matrix.laravel_version }}
          find: '*'
          replace: '0'

      - name: Install Laravel
        run: composer update --no-interaction illuminate/database:^${{ steps.laravel_version_cleaned.outputs.value }}
    
      - name: Run PHPUnit
        run: ./vendor/bin/phpunit


================================================
FILE: .gitignore
================================================
/vendor
/.idea
composer.lock
.php_cs.cache
.phpunit.result.cache


================================================
FILE: .php_cs
================================================
<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

/*
 * Define folders to fix
 */
$finder = Finder::create()
    ->in([
        __DIR__ .'/src',
        __DIR__ .'/tests',
    ]);
;

/*
 * Do the magic
 */
return Config::create()
    ->setUsingCache(false)
    ->setRules([
        '@PSR2'              => true,
        '@Symfony'           => true,
    ])
    ->setFinder($finder)
;

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Filip Horvat

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![Tests status](https://github.com/fico7489/laravel-eloquent-join/workflows/Test/badge.svg)

# Laravel Eloquent Join

This package introduces the join magic for eloquent models and relations.

## Introduction

Eloquent is a powerful ORM but its join capabilities are very poor.

#### First Eloquent Problem (sorting)

With laravel you can't perform sorting of the relationship fields without manually joining related table which is very awkward. Let me give you a few reasons why. If you have a table with **posts** and related **categories** your code might look like this:

```
$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();
```

1.The first problem is that you need to worry about select.
```
    ->select('posts.*')
```
Reason : without **select** id from the category can be selected and hydrated into the Post model.

2.The second problem is that you need to worry about **groupBy**.

    ->groupBy('posts.id');
    
Reason : if the relation is HasOne and there are more than one categories for the post, the query will return more rows for categories.

3.The third problem is that you need to change all other where clauses from : 
```
    ->where('date', $date)
```
to
```
    ->where('posts.date', $date)
```
Reason : a **post** and **category** can have "date" attribute and in that case without selecting an attribute with table "ambiguous column" error will be thrown.

4.The fourth problem is that you are using table names(not models) and this is also bad and awkward.
```
    ->where('posts.date', $date)
```
5.The fifth problem is that you need to worry about soft deletes for joined tables. If the **category** is using SoftDeletes trait you must add : 
```
    ->where('categories.deleted_at', '=', null)
```
This package will take care of all above problems for you. 
Unlike **sorting**, you can perform **filtering** on the relationship fields without joining related tables, but this package will give you the ability to do this easier.


#### Second Eloquent Problem (subqueries)

With laravel you can perform where on the relationship attribute but laravel will generate subqueries which are more slower than joins. 
With this package you will be available to perform where on the relationship with joins in an elegant way.


## Requirements

| Laravel Version | Package Tag | Supported | Development Branch
|-----------------|-------------|-----------| -----------|
| >= 5.5.0 | 4.* | yes | master
| < 5.5.0 | - | no | -

Package is also tested for SQLite, MySql and PostgreSql

## Installation & setup

1.Install package with composer
```
composer require fico7489/laravel-eloquent-join
```
With this statement, a composer will install highest available package version for your current laravel version.

2.Use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoinTrait trait in your base model or only in particular models.

```
...
use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model;

abstract class BaseModel extends Model
{
    use EloquentJoin;
...
```

3.IMPORTANT

For **MySql** make sure that **strict** configuration is set to **false**

config/database.php

```
        'mysql' => [
			...
            'strict'    => false,
			...
```

and that's it, you are ready to go.

## Options

Options can be set in the model  : 

```
class Seller extends BaseModel
{
    protected $useTableAlias = false;
    protected $appendRelationsCount = false;
    protected $leftJoin = false;
    protected $aggregateMethod = 'MAX';
```

or on query : 

```
    Order::setUseTableAlias(true)->get();
    Order::setAppendRelationsCount(true)->get();
    Order::setLeftJoin(true)->get();
    Order::setAggregateMethod(true)->get();
```

#### **useTableAlias**

Should we use an alias for joined tables (default = false)

With **true** query will look like this : 
```
select "sellers".* from "sellers" 
    left join "locations" as "5b5c093d2e00f" 
	...
```

With **false** query will look like this : 
```
select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...
```

Alias is a randomly generated string.

#### **appendRelationsCount**

Should we automatically append relation count field to results  (default = false)

With **true** query will look like this : 
```
select "sellers".*, count(locations.id) AS locations_count
	from "sellers" 
	left join "locations" as "5b5c093d2e00f" 
	...
```

Each **relation** is glued with an underscore and at the end **_count** prefix is added. For example for 

    ->joinRelations('seller.locations')
    
field would be __seller_locations_count__

#### **leftJoin**

Should we use **inner join** or **left join** (default = true)

```
select "sellers".* 
	from "sellers" 
	inner join "locations"                    
	...
```

vs

```
select "sellers".* 
	from "sellers" 
	left join "locations"                    
	...
```

#### **aggregateMethod**

Which aggregate method to use for ordering (default = 'MAX'). 

When join is performed on the joined table we must apply aggregate functions on the sorted field so we could perform group by clause and prevent duplication of results.

```
select "sellers".*, MAX("locations" ."number") AS sort
	from "sellers" 
	left join "locations" 
	group by "locations" ."id"
	order by sort
	...
```

Options are : **SUM**, **AVG**, **MAX**, **MIN**, **COUNT**

## Usage

### Currently available relations for join queries

* **BelongsTo**
* **HasOne**
* **HasMany**

### New clauses for eloquent builder on BelongsTo and HasOne relations : 

 **joinRelations($relations, $leftJoin = null)**

* ***$relations*** which relations to join
* ***$leftJoin*** use **left join** or **inner join**, default **left join**

**orderByJoin($column, $direction  = 'asc', $aggregateMethod = null)**

* ***$column*** and ***$direction***  arguments are the same as in default eloquent **orderBy()**
* ***$aggregateMethod*** argument defines which aggregate method to use ( **SUM**, **AVG**, **MAX**, **MIN**, **COUNT**), default **MAX**
    
**whereJoin($column, $operator, $value, $boolean = 'and')**

* arguments are the same as in default eloquent **where()**
    
**orWhereJoin($column, $operator, $value)**

* arguments are the same as in default eloquent **orWhere()**


**whereInJoin($column, $values, $boolean = 'and', $not = false)**

* arguments are the same as in default eloquent **whereIn()**

**whereNotInJoin($column, $values, $boolean = 'and')**

* arguments are the same as in default eloquent **whereNotIn()**

**orWhereInJoin($column, $values)**

* arguments are the same as in default eloquent **orWhereIn()**

**orWhereNotInJoin($column, $values)**

* arguments are the same as in default eloquent **orWhereNotIn()**


### Allowed clauses on BelongsTo, HasOne and HasMany relations on which you can use join clauses on the query

* Relations that you want to use for join queries can only have these clauses : **where**, **orWhere**, **withTrashed**, **onlyTrashed**, **withoutTrashed**. 
* Clauses **where** and **orWhere** can only have these variations 
** **->where($column, $operator, $value)** 
** **->where([$column => $value])**
* Closures are not allowed.
* Other clauses like **whereHas**, **orderBy** etc. are not allowed.
* You can add not allowed clauses on relations and use them in the normal eloquent way, but in these cases, you can't use those relations for join queries.

Allowed relation:

```
public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed();
}
```
Not allowed relation: 

```
public function locationPrimary()
{
    return $this->hasOne(Location::class)
        ->where('is_primary', '=', 1)
        ->orWhere('is_primary', '=', 1)
        ->withTrashed()
        ->whereHas('state', function($query){return $query;}
        ->orderBy('name')
        ->where(function($query){
            return $query->where('is_primary', '=', 1);
        });
}
```

The reason why the second relation is not allowed is that this package should apply all those clauses on the join clause,  eloquent use all those clauses isolated with subqueries NOT on join clause and that is more simpler to do.

You might get a picture that there are too many rules and restriction, but it is really not like that. 
Don't worry, if you do create the query that is not allowed appropriate exception will be thrown and you will know what happened.

### Other 

* If the model uses the SoftDelete trait, where deleted_at != null will be automatically applied
* You can combine new clauses unlimited times
* If you combine clauses more times on same relation package will join related table only once

```
Seller::whereJoin('city.title', '=', 'test')
    ->orWhereJoin('city.title', '=', 'test2');
```

* You can call  new clauses inside closures

```
Seller::where(function ($query) {
    $query
        ->whereJoin('city.title', '=', 'test')
        ->orWhereJoin('city.title', '=', 'test2');
});
```

* You can combine join clauses e.g. whereJoin() with eloquent clauses e.g. orderBy()

```
Seller::whereJoin('title', '=', 'test')
    ->whereJoin('city.title', '=', 'test')
    ->orderByJoin('city.title')
    ->get();
```

## See action on real example

Database schema : 

![Database schema](https://raw.githubusercontent.com/fico7489/laravel-eloquent-join/master/readme/era.png)

Models : 

```
class Seller extends BaseModel
{
    public function locations()
    {
        return $this->hasMany(Location::class);
    }
    
    public function locationPrimary()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1);
    }

    public function city()
    {
        return $this->belongsTo(City::class);
    }
```
```
class Location extends BaseModel
{
    public function locationAddressPrimary()
    {
        return $this->hasOne(LocationAddress::class)
            ->where('is_primary', '=', 1);
    }
    
```
```
class City extends BaseModel
{
    public function state()
    {
        return $this->belongsTo(State::class);
    }
}
```

### Join

##### Join BelongsTo
```Seller::joinRelations('city')```

##### Join HasOne
```Seller::joinRelations('locationPrimary')```

##### Join HasMany
```Seller::joinRelations('locations')```

##### Join Mixed
```Seller::joinRelations('city.state')```

### Join (mix left join)

```Seller::joinRelations('city', true)->joinRelations('city.state', false)```

### Join (multiple relationships)

```Seller::join(['city.state', 'locations'])```

### Ordering

##### Order BelongsTo
```Seller::orderByJoin('city.title')```

##### Order HasOne
```Seller::orderByJoin('locationPrimary.address')```

##### Order HasMany
```Seller::orderByJoin('locations.title')```

##### Order Mixed
```Seller::orderByJoin('city.state.title')```

### Ordering (special cases with aggregate functions)

##### Order by relation count
```Seller::orderByJoin('locations.id', 'asc', 'COUNT')```

##### Order by relation field SUM
```Seller::orderByJoin('locations.is_primary', 'asc', 'SUM')```

##### Order by relation field AVG
```Seller::orderByJoin('locations.is_primary', 'asc', 'AVG')```

##### Order by relation field MAX
```Seller::orderByJoin('locations.is_primary', 'asc', 'MAX')```

##### Order by relation field MIN
```Seller::orderByJoin('locations.is_primary', 'asc', 'MIN')```

### Filtering (where or orWhere)

##### Filter BelongsTo
```Seller::whereJoin('city.title', '=', 'test')```

##### Filter HasOne
```Seller::whereJoin('locationPrimary.address', '=', 'test')```

##### Filter HasMany
```Seller::whereJoin('locations.title', '=', 'test')```

##### Filter Mixed
```Seller::whereJoin('city.state.title', '=', 'test')```

### Relation count

```
$sellers = Seller::setAppendRelationsCount(true)->join('locations', '=', 'test')
    ->get();
    
foreach ($sellers as $seller){
    echo 'Number of location = ' . $seller->locations_count;
}

```

### Filter (mix left join)

```
Seller::joinRelations('city', true)
    ->joinRelations('city.state', false)
    ->whereJoin('city.id', '=', 1)
    ->orWhereJoin('city.state.id', '=', 1)
```

## Generated queries

Query : 
```
Order::whereJoin('seller.id', '=', 1)->get();
```

Sql : 
```
select "orders".* 
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "sellers"."id" = ? 
    and "orders"."deleted_at" is null 
    group by "orders"."id"
```

Query : 
```
Order::orderByJoin('seller.id', '=', 1)->get();
```

Sql : 
```
select "orders".*, MAX(sellers.id) as sort
    from "orders" 
    left join "sellers" on "sellers"."id" = "orders"."seller_id" 
    where "orders"."deleted_at" is null 
    group by "orders"."id"
    order by sort asc
```

## Elegance of package

Lets look how first example from documentation now looks like. This code : 

```
$posts = Post::select('posts.*')
    ->join('categories', 'categories.id', '=', 'posts.category_id')
    ->groupBy('posts.id')
    ->where('categories.deleted_at', '=', null)
    ->orderBy('categories.name');
    
if(request()->get('date')){
    $posts->where('date', $date)
}

$posts = $posts->get();
```

is now : 

```
$posts = Post::orderByJoin('category.name');
    
if(request()->get('date')){
    $posts->where('posts.date', $date)
}

$posts = $posts->get();
```

Both snippets do the same thing.

## Tests

This package is well covered with tests. If you want run tests just run **composer update** and then run tests with **"vendor/bin/phpunit"**

## Contribution

Feel free to create new issue for : 
* bug
* notice
* request new feature
* question
* clarification
* etc...



License
----

MIT


**Free Software, Hell Yeah!**


================================================
FILE: composer.json
================================================
{
    "name": "fico7489/laravel-eloquent-join",
    "description": "This package introduces the join magic for eloquent models and relations.",
    "keywords": [
        "laravel join",
        "laravel eloquent join",
        "laravel sort join",
        "laravel where join",
        "laravel join relation"
    ],
    "homepage": "https://github.com/fico7489/laravel-eloquent-join",
    "support": {
        "issues": "https://github.com/fico7489/laravel-eloquent-join/issues",
        "source": "https://github.com/fico7489/laravel-eloquent-join"
    },
    "license": "MIT",
    "authors": [
        {
            "name": "Filip Horvat",
            "email": "filip.horvat@am2studio.hr",
            "homepage": "http://am2studio.hr",
            "role": "Developer"
        }
    ],
    "require": {
        "illuminate/database": "^8.0|^9.0|^10.0|^11.0|^12.0"
    },
    "require-dev": {
        "orchestra/testbench": "*",
        "friendsofphp/php-cs-fixer" : "*",
        "phpunit/phpunit": "*"
    },
    "autoload": {
        "psr-4": {
            "Fico7489\\Laravel\\EloquentJoin\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Fico7489\\Laravel\\EloquentJoin\\Tests\\": "tests/"
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/5.7/phpunit.xsd"
         backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         bootstrap="vendor/autoload.php"
>
    <php>
        <ini name="error_reporting" value="-1"/>
    </php>

    <testsuites>
        <testsuite name="Remember Upload Service Testsuit">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>./src</directory>
            <exclude>
                <directory>./tests</directory>
                <directory>./vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

================================================
FILE: src/EloquentJoinBuilder.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin;

use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidAggregateMethod;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidDirection;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelation;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationClause;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationGlobalScope;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationWhere;
use Fico7489\Laravel\EloquentJoin\Relations\BelongsToJoin;
use Fico7489\Laravel\EloquentJoin\Relations\HasManyJoin;
use Fico7489\Laravel\EloquentJoin\Relations\HasOneJoin;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Database\Query\JoinClause;

class EloquentJoinBuilder extends Builder
{
    //constants
    const AGGREGATE_SUM = 'SUM';
    const AGGREGATE_AVG = 'AVG';
    const AGGREGATE_MAX = 'MAX';
    const AGGREGATE_MIN = 'MIN';
    const AGGREGATE_COUNT = 'COUNT';

    //use table alias for join (real table name or random sha1)
    protected $useTableAlias = false;

    //appendRelationsCount
    protected $appendRelationsCount = false;

    //leftJoin
    protected $leftJoin = true;

    //aggregate method
    protected $aggregateMethod = self::AGGREGATE_MAX;

    //base builder
    protected $baseBuilder;

    //store if ->select(...) is already called on builder (we want only one groupBy())
    protected $selected = false;

    //store joined tables, we want join table only once (e.g. when you call orderByJoin more time)
    protected $joinedTables = [];

    //store clauses on relation for join
    public $relationClauses = [];

    //query methods
    public function where($column, $operator = null, $value = null, $boolean = 'and')
    {
        if ($column instanceof \Closure) {
            $query = $this->model->newModelQuery();
            $baseBuilderCurrent = $this->baseBuilder ? $this->baseBuilder : $this;
            $query->baseBuilder = $baseBuilderCurrent;

            $column($query);

            $this->query->addNestedWhereQuery($query->getQuery(), $boolean);
        } else {
            $this->query->where(...func_get_args());
        }

        return $this;
    }

    public function whereJoin($column, $operator, $value, $boolean = 'and')
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->where($column, $operator, $value, $boolean);
    }

    public function orWhereJoin($column, $operator, $value)
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->orWhere($column, $operator, $value);
    }

    public function whereInJoin($column, $values, $boolean = 'and', $not = false)
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->whereIn($column, $values, $boolean, $not);
    }

    public function whereNotInJoin($column, $values, $boolean = 'and')
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->whereNotIn($column, $values, $boolean);
    }

    public function orWhereInJoin($column, $values)
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->orWhereIn($column, $values);
    }

    public function orWhereNotInJoin($column, $values)
    {
        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);

        return $this->orWhereNotIn($column, $values);
    }

    public function orderByJoin($column, $direction = 'asc', $aggregateMethod = null)
    {
        $direction = strtolower($direction);
        $this->checkDirection($direction);
        $dotPos = strrpos($column, '.');

        $query = $this->baseBuilder ? $this->baseBuilder : $this;
        $column = $query->performJoin($column);
        if (false !== $dotPos) {
            //order by related table field
            $aggregateMethod = $aggregateMethod ? $aggregateMethod : $this->aggregateMethod;
            $this->checkAggregateMethod($aggregateMethod);

            $sortsCount = count($this->query->orders ?? []);
            $sortAlias = 'sort'.(0 == $sortsCount ? '' : ($sortsCount + 1));

            $grammar = \DB::query()->getGrammar();
            $query->selectRaw($aggregateMethod.'('.$grammar->wrap($column).') as '.$sortAlias);

            return $this->orderByRaw($sortAlias.' '.$direction);
        }

        //order by base table field

        return $this->orderBy($column, $direction);
    }

    /**
     * Joining relations.
     *
     * @param string|array $relations
     * @param bool|null    $leftJoin
     *
     * @return $this
     *
     * @throws InvalidRelation
     */
    public function joinRelations($relations, $leftJoin = null)
    {
        $leftJoin = null !== $leftJoin ? $leftJoin : $this->leftJoin;
        $query = $this->baseBuilder ? $this->baseBuilder : $this;

        if (is_array($relations)) {
            foreach ($relations as $relation) {
                $query->joinRelations($relation, $leftJoin);
            }
        } else {
            $query->performJoin($relations.'.FAKE_FIELD', $leftJoin);
        }

        return $this;
    }

    //helpers methods
    protected function performJoin($relations, $leftJoin = null)
    {
        //detect join method
        $leftJoin = null !== $leftJoin ? $leftJoin : $this->leftJoin;
        $joinMethod = $leftJoin ? 'leftJoin' : 'join';

        //detect current model data
        $relations = explode('.', $relations);
        $column = end($relations);
        $baseModel = $this->getModel();
        $baseTable = $baseModel->getTable();
        $basePrimaryKey = $baseModel->getKeyName();

        $currentModel = $baseModel;
        $currentTableAlias = $baseTable;

        $relationsAccumulated = [];
        foreach ($relations as $relation) {
            if ($relation == $column) {
                //last item in $relations argument is sort|where column
                break;
            }

            /** @var Relation $relatedRelation */
            $relatedRelation = $currentModel->$relation();
            $relatedModel = $relatedRelation->getRelated();
            $relatedPrimaryKey = $relatedModel->getKeyName();
            $relatedTable = $relatedModel->getTable();
            $relatedTableAlias = $this->useTableAlias ? sha1($relatedTable.rand()) : $relatedTable;

            $relationsAccumulated[] = $relatedTableAlias;
            $relationAccumulatedString = implode('_', $relationsAccumulated);

            //relations count
            if ($this->appendRelationsCount) {
                $this->selectRaw('COUNT('.$relatedTableAlias.'.'.$relatedPrimaryKey.') as '.$relationAccumulatedString.'_count');
            }

            if (!in_array($relationAccumulatedString, $this->joinedTables)) {
                $joinQuery = $relatedTable.($this->useTableAlias ? ' as '.$relatedTableAlias : '');
                if ($relatedRelation instanceof BelongsToJoin) {
                    $relatedKey = is_callable([$relatedRelation, 'getQualifiedForeignKeyName']) ? $relatedRelation->getQualifiedForeignKeyName() : $relatedRelation->getQualifiedForeignKey();
                    $relatedKey = last(explode('.', $relatedKey));
                    $ownerKey = is_callable([$relatedRelation, 'getOwnerKeyName']) ? $relatedRelation->getOwnerKeyName() : $relatedRelation->getOwnerKey();

                    $this->$joinMethod($joinQuery, function ($join) use ($relatedRelation, $relatedTableAlias, $relatedKey, $currentTableAlias, $ownerKey) {
                        $join->on($relatedTableAlias.'.'.$ownerKey, '=', $currentTableAlias.'.'.$relatedKey);

                        $this->joinQuery($join, $relatedRelation, $relatedTableAlias);
                    });
                } elseif ($relatedRelation instanceof HasOneJoin || $relatedRelation instanceof HasManyJoin) {
                    $relatedKey = $relatedRelation->getQualifiedForeignKeyName();
                    $relatedKey = last(explode('.', $relatedKey));
                    $localKey = $relatedRelation->getQualifiedParentKeyName();
                    $localKey = last(explode('.', $localKey));

                    $this->$joinMethod($joinQuery, function ($join) use ($relatedRelation, $relatedTableAlias, $relatedKey, $currentTableAlias, $localKey) {
                        $join->on($relatedTableAlias.'.'.$relatedKey, '=', $currentTableAlias.'.'.$localKey);

                        $this->joinQuery($join, $relatedRelation, $relatedTableAlias);
                    });
                } else {
                    throw new InvalidRelation();
                }
            }

            $currentModel = $relatedModel;
            $currentTableAlias = $relatedTableAlias;

            $this->joinedTables[] = implode('_', $relationsAccumulated);
        }

        if (!$this->selected && count($relations) > 1) {
            $this->selected = true;
            $this->selectRaw($baseTable.'.*');
            $this->groupBy($baseTable.'.'.$basePrimaryKey);
        }

        return $currentTableAlias.'.'.$column;
    }

    protected function joinQuery($join, $relation, $relatedTableAlias)
    {
        /** @var Builder $relationQuery */
        $relationBuilder = $relation->getQuery();

        //apply clauses on relation
        if (isset($relationBuilder->relationClauses)) {
            foreach ($relationBuilder->relationClauses as $clause) {
                foreach ($clause as $method => $params) {
                    $this->applyClauseOnRelation($join, $method, $params, $relatedTableAlias);
                }
            }
        }

        //apply global SoftDeletingScope
        foreach ($relationBuilder->scopes as $scope) {
            if ($scope instanceof SoftDeletingScope) {
                $this->applyClauseOnRelation($join, 'withoutTrashed', [], $relatedTableAlias);
            } else {
                throw new InvalidRelationGlobalScope();
            }
        }
    }

    private function applyClauseOnRelation(JoinClause $join, string $method, array $params, string $relatedTableAlias)
    {
        if (in_array($method, ['where', 'orWhere'])) {
            try {
                if (is_array($params[0])) {
                    foreach ($params[0] as $k => $param) {
                        $params[0][$relatedTableAlias.'.'.$k] = $param;
                        unset($params[0][$k]);
                    }
                } elseif (is_callable($params[0])) {
                    throw new InvalidRelationWhere();
                } else {
                    $params[0] = $relatedTableAlias.'.'.$params[0];
                }

                call_user_func_array([$join, $method], $params);
            } catch (\Exception $e) {
                throw new InvalidRelationWhere();
            }
        } elseif (in_array($method, ['withoutTrashed', 'onlyTrashed', 'withTrashed'])) {
            if ('withTrashed' == $method) {
                //do nothing
            } elseif ('withoutTrashed' == $method) {
                call_user_func_array([$join, 'where'], [$relatedTableAlias.'.deleted_at', '=', null]);
            } elseif ('onlyTrashed' == $method) {
                call_user_func_array([$join, 'where'], [$relatedTableAlias.'.deleted_at', '<>', null]);
            }
        } else {
            throw new InvalidRelationClause();
        }
    }

    private function checkAggregateMethod($aggregateMethod)
    {
        if (!in_array($aggregateMethod, [
            self::AGGREGATE_SUM,
            self::AGGREGATE_AVG,
            self::AGGREGATE_MAX,
            self::AGGREGATE_MIN,
            self::AGGREGATE_COUNT,
        ])) {
            throw new InvalidAggregateMethod();
        }
    }

    private function checkDirection($direction)
    {
        if (!in_array($direction, ['asc', 'desc'], true)) {
            throw new InvalidDirection();
        }
    }

    //getters and setters
    public function isUseTableAlias(): bool
    {
        return $this->useTableAlias;
    }

    public function setUseTableAlias(bool $useTableAlias)
    {
        $this->useTableAlias = $useTableAlias;

        return $this;
    }

    public function isLeftJoin(): bool
    {
        return $this->leftJoin;
    }

    public function setLeftJoin(bool $leftJoin)
    {
        $this->leftJoin = $leftJoin;

        return $this;
    }

    public function isAppendRelationsCount(): bool
    {
        return $this->appendRelationsCount;
    }

    public function setAppendRelationsCount(bool $appendRelationsCount)
    {
        $this->appendRelationsCount = $appendRelationsCount;

        return $this;
    }

    public function getAggregateMethod(): string
    {
        return $this->aggregateMethod;
    }

    public function setAggregateMethod(string $aggregateMethod)
    {
        $this->checkAggregateMethod($aggregateMethod);
        $this->aggregateMethod = $aggregateMethod;

        return $this;
    }
}


================================================
FILE: src/Exceptions/InvalidAggregateMethod.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidAggregateMethod extends \Exception
{
    public $message = 'Invalid aggregate method';
}


================================================
FILE: src/Exceptions/InvalidDirection.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidDirection extends \Exception
{
    public $message = 'Invalid direction. Order direction must be either \'asc\' or \'desc\'';
}


================================================
FILE: src/Exceptions/InvalidRelation.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidRelation extends \Exception
{
    public $message = 'Package allows only following relations : BelongsTo, HasOne and HasMany.';
}


================================================
FILE: src/Exceptions/InvalidRelationClause.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidRelationClause extends \Exception
{
    public $message = 'Package allows only following clauses on relation : where, orWhere, withTrashed, onlyTrashed and withoutTrashed.';
}


================================================
FILE: src/Exceptions/InvalidRelationGlobalScope.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidRelationGlobalScope extends \Exception
{
    public $message = 'Package allows only SoftDeletingScope global scope.';
}


================================================
FILE: src/Exceptions/InvalidRelationWhere.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Exceptions;

class InvalidRelationWhere extends \Exception
{
    public $message = 'Package allows only following where(orWhere) clauses type on relation : ->where($column, $operator, $value) and ->where([$column => $value]).';
}


================================================
FILE: src/Relations/BelongsToJoin.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Relations;

use Fico7489\Laravel\EloquentJoin\Traits\JoinRelationTrait;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class BelongsToJoin extends BelongsTo
{
    use JoinRelationTrait;
}


================================================
FILE: src/Relations/HasManyJoin.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Relations;

use Fico7489\Laravel\EloquentJoin\Traits\JoinRelationTrait;
use Illuminate\Database\Eloquent\Relations\HasMany;

class HasManyJoin extends HasMany
{
    use JoinRelationTrait;
}


================================================
FILE: src/Relations/HasOneJoin.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Relations;

use Fico7489\Laravel\EloquentJoin\Traits\JoinRelationTrait;
use Illuminate\Database\Eloquent\Relations\HasOne;

class HasOneJoin extends HasOne
{
    use JoinRelationTrait;
}


================================================
FILE: src/Traits/EloquentJoin.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Traits;

use Fico7489\Laravel\EloquentJoin\EloquentJoinBuilder;

/**
 * Trait EloquentJoin.
 *
 * @method static EloquentJoinBuilder joinRelations($relations, $leftJoin = null)
 * @method static EloquentJoinBuilder whereJoin($column, $operator, $value, $boolean = 'and')
 * @method static EloquentJoinBuilder orWhereJoin($column, $operator, $value)
 * @method static EloquentJoinBuilder whereInJoin($column, $values, $boolean = 'and', $not = false)
 * @method static EloquentJoinBuilder whereNotInJoin($column, $values, $boolean = 'and')
 * @method static EloquentJoinBuilder orWhereInJoin($column, $values)
 * @method static EloquentJoinBuilder orWhereNotInJoin($column, $values)
 * @method static EloquentJoinBuilder orderByJoin($column, $direction = 'asc', $aggregateMethod = null)
 */
trait EloquentJoin
{
    use ExtendRelationsTrait;

    /**
     * @param $query
     *
     * @return EloquentJoinBuilder
     */
    public function newEloquentBuilder($query)
    {
        $newEloquentBuilder = new EloquentJoinBuilder($query);

        if (isset($this->useTableAlias)) {
            $newEloquentBuilder->setUseTableAlias($this->useTableAlias);
        }

        if (isset($this->appendRelationsCount)) {
            $newEloquentBuilder->setAppendRelationsCount($this->appendRelationsCount);
        }

        if (isset($this->leftJoin)) {
            $newEloquentBuilder->setLeftJoin($this->leftJoin);
        }

        if (isset($this->aggregateMethod)) {
            $newEloquentBuilder->setAggregateMethod($this->aggregateMethod);
        }

        return $newEloquentBuilder;
    }
}


================================================
FILE: src/Traits/ExtendRelationsTrait.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Traits;

use Fico7489\Laravel\EloquentJoin\Relations\BelongsToJoin;
use Fico7489\Laravel\EloquentJoin\Relations\HasManyJoin;
use Fico7489\Laravel\EloquentJoin\Relations\HasOneJoin;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

trait ExtendRelationsTrait
{
    protected function newBelongsTo(Builder $query, Model $child, $foreignKey, $ownerKey, $relation)
    {
        return new BelongsToJoin($query, $child, $foreignKey, $ownerKey, $relation);
    }

    protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey)
    {
        return new HasOneJoin($query, $parent, $foreignKey, $localKey);
    }

    protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey)
    {
        return new HasManyJoin($query, $parent, $foreignKey, $localKey);
    }
}


================================================
FILE: src/Traits/JoinRelationTrait.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Traits;

use Fico7489\Laravel\EloquentJoin\EloquentJoinBuilder;

trait JoinRelationTrait
{
    /**
     * Handle dynamic method calls to the relationship.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if ($this->getQuery() instanceof EloquentJoinBuilder) {
            $this->getQuery()->relationClauses[] = [$method => $parameters];
        }

        return parent::__call($method, $parameters);
    }
}


================================================
FILE: tests/Models/BaseModel.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Fico7489\Laravel\EloquentJoin\Traits\EloquentJoin;
use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    use EloquentJoin;
}


================================================
FILE: tests/Models/City.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class City extends BaseModel
{
    use SoftDeletes;

    protected $table = 'cities';

    protected $fillable = ['name'];

    public function state()
    {
        return $this->belongsTo(State::class);
    }

    public function zipCodePrimary()
    {
        return $this->hasOne(ZipCode::class)
            ->where('is_primary', '=', 1);
    }

    public function sellers()
    {
        return $this->belongsToMany(Seller::class, 'locations', 'seller_id', 'city_id');
    }

    public function zipCodes()
    {
        return $this->hasMany(ZipCode::class);
    }
}


================================================
FILE: tests/Models/Integration.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class Integration extends BaseModel
{
    use SoftDeletes;

    protected $table = 'integrations';

    protected $fillable = ['name'];
}


================================================
FILE: tests/Models/Key/Location.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models\Key;

use Fico7489\Laravel\EloquentJoin\Tests\Models\BaseModel;

class Location extends BaseModel
{
    protected $primaryKey = 'id_location_primary';

    protected $table = 'key_locations';

    protected $fillable = ['address', 'id_seller_foreign'];
}


================================================
FILE: tests/Models/Key/Order.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models\Key;

use Fico7489\Laravel\EloquentJoin\Tests\Models\BaseModel;

class Order extends BaseModel
{
    protected $primaryKey = 'id_order_primary';

    protected $table = 'key_orders';

    protected $fillable = ['number', 'id_seller_foreign'];

    public function seller()
    {
        return $this->belongsTo(Seller::class, 'id_seller_foreign', 'id_seller_primary');
    }

    public function sellerOwner()
    {
        return $this->belongsTo(Seller::class, 'id_seller_foreign', 'id_seller_owner');
    }
}


================================================
FILE: tests/Models/Key/Seller.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models\Key;

use Fico7489\Laravel\EloquentJoin\Tests\Models\BaseModel;

class Seller extends BaseModel
{
    protected $primaryKey = 'id_seller_primary';

    protected $table = 'key_sellers';

    protected $fillable = ['title'];

    public function location()
    {
        return $this->hasOne(Location::class, 'id_seller_foreign', 'id_seller_primary');
    }

    public function locations()
    {
        return $this->hasMany(Location::class, 'id_seller_foreign', 'id_seller_primary');
    }

    public function locationOwner()
    {
        return $this->hasOne(Location::class, 'id_seller_foreign', 'id_seller_owner');
    }

    public function locationsOwner()
    {
        return $this->hasMany(Location::class, 'id_seller_foreign', 'id_seller_owner');
    }
}


================================================
FILE: tests/Models/Location.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class Location extends BaseModel
{
    use SoftDeletes;

    protected $table = 'locations';

    protected $fillable = ['address', 'seller_id', 'is_primary', 'is_secondary', 'city_id'];

    public function seller()
    {
        return $this->belongsTo(Seller::class);
    }

    public function city()
    {
        return $this->belongsTo(City::class);
    }

    public function locationAddressPrimary()
    {
        return $this->hasOne(LocationAddress::class)
            ->where('is_primary', '=', 1);
    }

    public function integrations()
    {
        return $this->hasMany(Integration::class);
    }
}


================================================
FILE: tests/Models/LocationAddress.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class LocationAddress extends BaseModel
{
    use SoftDeletes;

    protected $table = 'location_addresses';

    protected $fillable = ['address', 'is_primary'];

    public function users()
    {
        return $this->hasMany(User::class);
    }
}


================================================
FILE: tests/Models/LocationWithGlobalScope.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Fico7489\Laravel\EloquentJoin\Tests\Scope\TestExceptionScope;
use Illuminate\Database\Eloquent\SoftDeletes;

class LocationWithGlobalScope extends BaseModel
{
    use SoftDeletes;

    protected $table = 'locations';

    protected static function boot()
    {
        parent::boot();

        static::addGlobalScope(new TestExceptionScope());
    }
}


================================================
FILE: tests/Models/Order.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class Order extends BaseModel
{
    use SoftDeletes;

    protected $table = 'orders';

    protected $fillable = ['number', 'seller_id'];

    public function seller()
    {
        return $this->belongsTo(Seller::class);
    }
}


================================================
FILE: tests/Models/OrderItem.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class OrderItem extends BaseModel
{
    use SoftDeletes;

    protected $table = 'order_items';

    protected $fillable = ['name', 'order_id'];

    public function order()
    {
        return $this->belongsTo(Order::class);
    }

    public function orderWithTrashed()
    {
        return $this->belongsTo(Order::class, 'order_id')
            ->withTrashed();
    }

    public function orderOnlyTrashed()
    {
        return $this->belongsTo(Order::class, 'order_id')
            ->onlyTrashed();
    }
}


================================================
FILE: tests/Models/Seller.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

class Seller extends BaseModel
{
    protected $table = 'sellers';

    protected $fillable = ['title', 'deleted_at'];

    public function location()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 0)
            ->where('is_secondary', '=', 0);
    }

    public function locations()
    {
        return $this->hasMany(Location::class);
    }

    public function locationPrimary()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1);
    }

    public function locationPrimaryInvalid()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1)
            ->orderBy('is_primary');
    }

    public function locationPrimaryInvalid2()
    {
        return $this->hasOne(Location::class)
            ->where(function ($query) {
                return $query->where(['id' => 1]);
            });
    }

    public function locationPrimaryInvalid3()
    {
        return $this->hasOne(LocationWithGlobalScope::class);
    }

    public function locationSecondary()
    {
        return $this->hasOne(Location::class)
            ->where('is_secondary', '=', 1);
    }

    public function locationPrimaryOrSecondary()
    {
        return $this->hasOne(Location::class)
            ->where('is_primary', '=', 1)
            ->orWhere('is_secondary', '=', 1);
    }

    public function city()
    {
        return $this->belongsTo(City::class);
    }
}


================================================
FILE: tests/Models/State.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class State extends BaseModel
{
    use SoftDeletes;

    protected $table = 'states';

    protected $fillable = ['name'];

    public function cities()
    {
        return $this->hasMany(City::class);
    }
}


================================================
FILE: tests/Models/User.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class User extends BaseModel
{
    use SoftDeletes;

    protected $table = 'users';

    protected $fillable = ['name'];
}


================================================
FILE: tests/Models/ZipCode.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Models;

use Illuminate\Database\Eloquent\SoftDeletes;

class ZipCode extends BaseModel
{
    use SoftDeletes;

    protected $table = 'zip_codes';

    protected $fillable = ['name', 'is_primary'];
}


================================================
FILE: tests/Scope/TestExceptionScope.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Scope;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class TestExceptionScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('test', '=', 'test');
    }
}


================================================
FILE: tests/ServiceProvider.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests;

class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
    public function register()
    {
        //register
    }

    public function boot()
    {
        $this->loadMigrationsFrom(__DIR__.'/database/migrations/');
    }

    protected function loadMigrationsFrom($path)
    {
        $_ENV['type'] = 'sqlite';  //sqlite, mysql, pgsql

        \Artisan::call('migrate', ['--database' => $_ENV['type']]);

        $migrator = $this->app->make('migrator');
        $migrator->run($path);
    }
}


================================================
FILE: tests/TestCase.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Location;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\Models\OrderItem;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;

abstract class TestCase extends \Orchestra\Testbench\TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        $seller = Seller::create(['title' => 1]);
        $seller2 = Seller::create(['title' => 2]);
        $seller3 = Seller::create(['title' => 3]);
        $seller4 = Seller::create(['title' => 4]);

        Location::create(['address' => 1, 'seller_id' => $seller->id]);
        Location::create(['address' => 2, 'seller_id' => $seller2->id]);
        Location::create(['address' => 3, 'seller_id' => $seller3->id]);
        Location::create(['address' => 3, 'seller_id' => $seller3->id]);

        Location::create(['address' => 4, 'seller_id' => $seller3->id, 'is_primary' => 1]);
        Location::create(['address' => 5, 'seller_id' => $seller3->id, 'is_secondary' => 1]);

        $order = Order::create(['number' => '1', 'seller_id' => $seller->id]);
        $order2 = Order::create(['number' => '2', 'seller_id' => $seller2->id]);
        $order3 = Order::create(['number' => '3', 'seller_id' => $seller3->id]);

        OrderItem::create(['name' => '1', 'order_id' => $order->id]);
        OrderItem::create(['name' => '2', 'order_id' => $order2->id]);
        OrderItem::create(['name' => '3', 'order_id' => $order3->id]);

        $this->startListening();
    }

    protected function startListening()
    {
        \DB::enableQueryLog();
    }

    protected function fetchLastLog()
    {
        $log = \DB::getQueryLog();

        return end($log);
    }

    protected function fetchQuery()
    {
        $query = $this->fetchLastLog()['query'];
        $bindings = $this->fetchLastLog()['bindings'];

        foreach ($bindings as $binding) {
            $binding = is_string($binding) ? ('"'.$binding.'"') : $binding;
            $query = preg_replace('/\?/', $binding, $query, 1);
        }

        return $query;
    }

    protected function fetchBindings()
    {
        return $this->fetchLastLog()['bindings'];
    }

    protected function getEnvironmentSetUp($app)
    {
        // Setup default database to use sqlite :memory:
        $app['config']->set('database.connections.sqlite', [
            'driver' => 'sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ]);

        $app['config']->set('database.connections.mysql', [
            'driver' => 'mysql',
            'host' => 'localhost',
            'database' => 'join',
            'username' => 'root',
            'password' => '',
            'charset' => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'strict' => true,
        ]);

        $app['config']->set('database.connections.pgsql', [
            'driver' => 'pgsql',
            'host' => 'localhost',
            'database' => 'join',
            'username' => 'postgres',
            'password' => 'root',
            'charset' => 'utf8',
            'prefix' => '',
            'schema' => 'public',
            'sslmode' => 'prefer',
        ]);

        $app['config']->set('database.default', env('type', 'sqlite'));
    }

    protected function getPackageProviders($app)
    {
        return [ServiceProvider::class];
    }

    protected function assertQueryMatches($expected, $actual)
    {
        $actual = preg_replace('/\s\s+/', ' ', $actual);
        $actual = str_replace(['\n', '\r'], '', $actual);

        $expected = preg_replace('/\s\s+/', ' ', $expected);
        $expected = str_replace(['\n', '\r'], '', $expected);
        $expected = '/'.$expected.'/';
        $expected = preg_quote($expected);
        if ('mysql' == $_ENV['type']) {
            $expected = str_replace(['"'], '`', $expected);
        }

        $this->assertMatchesRegularExpression($expected, $actual);
    }
}


================================================
FILE: tests/Tests/AggregateJoinTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\EloquentJoinBuilder;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class AggregateJoinTest extends TestCase
{
    private $queryTest = 'select orders.*, SUM("sellers"."id") as sort 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "orders"."deleted_at" is null 
            group by "orders"."id" 
            order by sort asc';

    public function testAvg()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc', EloquentJoinBuilder::AGGREGATE_SUM)
            ->get();

        $queryTest = str_replace(EloquentJoinBuilder::AGGREGATE_SUM, EloquentJoinBuilder::AGGREGATE_SUM, $this->queryTest);
        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testSum()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc', EloquentJoinBuilder::AGGREGATE_AVG)
            ->get();

        $queryTest = str_replace(EloquentJoinBuilder::AGGREGATE_SUM, EloquentJoinBuilder::AGGREGATE_AVG, $this->queryTest);
        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testMax()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc', EloquentJoinBuilder::AGGREGATE_MAX)
            ->get();

        $queryTest = str_replace(EloquentJoinBuilder::AGGREGATE_SUM, EloquentJoinBuilder::AGGREGATE_MAX, $this->queryTest);
        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testMin()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc', EloquentJoinBuilder::AGGREGATE_MIN)
            ->get();

        $queryTest = str_replace(EloquentJoinBuilder::AGGREGATE_SUM, EloquentJoinBuilder::AGGREGATE_MIN, $this->queryTest);
        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testCount()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc', EloquentJoinBuilder::AGGREGATE_COUNT)
            ->get();

        $queryTest = str_replace(EloquentJoinBuilder::AGGREGATE_SUM, EloquentJoinBuilder::AGGREGATE_COUNT, $this->queryTest);
        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/AppendRelationsCountTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class AppendRelationsCountTest extends TestCase
{
    public function testWhere()
    {
        Order::setAppendRelationsCount(true)->joinRelations('seller.locationPrimary.locationAddressPrimary')->get();

        $queryTest = 'select COUNT(sellers.id) as sellers_count, COUNT(locations.id) as sellers_locations_count, COUNT(location_addresses.id) as sellers_locations_location_addresses_count, orders.*
            from "orders"
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            and "locations"."deleted_at" is null 
            left join "location_addresses" on "location_addresses"."location_id" = "locations"."id" 
            and "location_addresses"."is_primary" = 1 
            and "location_addresses"."deleted_at" is null 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/JoinRelationsTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class JoinRelationsTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/OrWhereInTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class OrWhereInTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereInJoin('seller.id', [1, 2])
            ->orWhereInJoin('seller.id', [3, 4])
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where ("sellers"."id" in (1, 2) 
                or 
                "sellers"."id" in (3, 4))
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/OrWhereNotInTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class OrWhereNotInTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereInJoin('seller.id', [1, 2])
            ->orWhereNotInJoin('seller.id', [3, 4])
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where ("sellers"."id" in (1, 2) 
                or 
                "sellers"."id" not in (3, 4))
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/OrWhereTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class OrWhereTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereJoin('seller.id', '=', 1)
            ->orWhereJoin('seller.id', '=', 2)
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where ("sellers"."id" = 1 or "sellers"."id" = 2) 
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/OrderByTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class OrderByTest extends TestCase
{
    public function testOrderBy()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc')
            ->get();

        $queryTest = 'select orders.*, MAX("sellers"."id") as sort
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "orders"."deleted_at" is null 
            group by "orders"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testOrderByMultiple()
    {
        Order::joinRelations('seller')
            ->orderByJoin('seller.id', 'asc')
            ->orderByJoin('seller.title', 'desc')
            ->get();

        $queryTest = 'select orders.*, MAX("sellers"."id") as sort, MAX("sellers"."title") as sort2
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "orders"."deleted_at" is null 
            group by "orders"."id"
            order by sort asc, sort2 desc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/WhereInTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class WhereInTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereInJoin('seller.id', [1, 2])
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "sellers"."id" in (1, 2)
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/WhereNotInTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class WhereNotInTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereNotInJoin('seller.id', [1, 2])
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "sellers"."id" not in (1, 2)
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Clauses/WhereTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Clauses;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class WhereTest extends TestCase
{
    public function testWhere()
    {
        Order::joinRelations('seller')
            ->whereJoin('seller.id', '=', 1)
            ->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "sellers"."id" = 1 
            and "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/ClosureOnRelationTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class ClosureOnRelationTest extends TestCase
{
    public function testWhereOnRelationWithOrderByJoin()
    {
        //location have two where  ['is_primary => 0', 'is_secondary' => 0]
        $items = Seller::orderByJoin('location.id', 'desc')->get();
        $queryTest = 'select sellers.*, MAX("locations"."id") as sort from "sellers" 
            left join "locations" 
            on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 0 
            and "locations"."is_secondary" = 0 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"
            order by sort desc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());

        //locationPrimary have one where ['is_primary => 1']
        $items = Seller::orderByJoin('locationPrimary.id', 'desc')->get();
        $queryTest = 'select sellers.*, MAX("locations"."id") as sort from "sellers" 
            left join "locations" 
            on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"
            order by sort desc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());

        //locationPrimary have one where ['is_secondary => 1']
        $items = Seller::orderByJoin('locationSecondary.id', 'desc')->get();
        $queryTest = 'select sellers.*, MAX("locations"."id") as sort from "sellers" 
            left join "locations" 
            on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_secondary" = 1 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"
            order by sort desc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());

        //locationPrimary have one where ['is_primary => 1'] and one orWhere ['is_secondary => 1']
        $items = Seller::orderByJoin('locationPrimaryOrSecondary.id', 'desc')->get();
        $queryTest = 'select sellers.*, MAX("locations"."id") as sort from "sellers" 
            left join "locations" 
            on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            or "locations"."is_secondary" = 1 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"
            order by sort desc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testWhereOnRelationWithoutOrderByJoin()
    {
        $seller = Seller::find(1);

        $seller->locationPrimary;
        $queryTest = 'select * from "locations" 
            where "locations"."seller_id" = 1 
            and "locations"."seller_id" is not null 
            and "is_primary" = 1
            and "locations"."deleted_at" is null 
            limit 1';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());

        $seller->locationPrimary()->where(['is_secondary' => 1])->get();
        $queryTest = 'select * from "locations" 
            where "locations"."seller_id" = 1 
            and "locations"."seller_id" is not null 
            and "is_primary" = 1
            and ("is_secondary" = 1)
            and "locations"."deleted_at" is null';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/ClosureTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\OrderItem;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class ClosureTest extends TestCase
{
    public function testNestOne()
    {
        OrderItem::where(function ($query) {
            $query
                ->orWhereJoin('order.id', '=', 1)
                ->orWhereJoin('order.id', '=', 2);
        })->get();

        $queryTest = 'select order_items.* 
            from "order_items" 
            left join "orders" on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null 
            where ("orders"."id" = 1 or "orders"."id" = 2) 
            and "order_items"."deleted_at" is null';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testNestTwo()
    {
        OrderItem::where(function ($query) {
            $query
                ->orWhereJoin('order.id', '=', 1)
                ->orWhereJoin('order.id', '=', 2)
                ->where(function ($query) {
                    $query->orWhereJoin('order.seller.locationPrimary.id', '=', 3);
                });
        })->get();

        $queryTest = 'select order_items.* 
            from "order_items" 
            left join "orders" on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            and "locations"."deleted_at" is null 
            where ("orders"."id" = 1 or "orders"."id" = 2 
            and ("locations"."id" = 3)) 
            and "order_items"."deleted_at" is null';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/ExceptionTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidAggregateMethod;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidDirection;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelation;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationClause;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationGlobalScope;
use Fico7489\Laravel\EloquentJoin\Exceptions\InvalidRelationWhere;
use Fico7489\Laravel\EloquentJoin\Tests\Models\City;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class ExceptionTest extends TestCase
{
    public function testInvalidRelation()
    {
        try {
            City::whereJoin('sellers.id', '=', 'test')->get();
        } catch (InvalidRelation $e) {
            $this->assertEquals((new InvalidRelation())->message, $e->getMessage());

            return;
        }

        $this->assertTrue(false);
    }

    public function testInvalidRelationWhere()
    {
        try {
            Seller::whereJoin('locationPrimaryInvalid2.name', '=', 'test')->get();
        } catch (InvalidRelationWhere $e) {
            $this->assertEquals((new InvalidRelationWhere())->message, $e->getMessage());

            return;
        }

        $this->assertTrue(false);
    }

    public function testInvalidRelationClause()
    {
        try {
            Seller::whereJoin('locationPrimaryInvalid.name', '=', 'test')->get();
        } catch (InvalidRelationClause $e) {
            $this->assertEquals((new InvalidRelationClause())->message, $e->getMessage());

            return;
        }

        $this->assertTrue(false);
    }

    public function testInvalidRelationGlobalScope()
    {
        try {
            Seller::whereJoin('locationPrimaryInvalid3.id', '=', 'test')->get();
        } catch (InvalidRelationGlobalScope $e) {
            $this->assertEquals((new InvalidRelationGlobalScope())->message, $e->getMessage());

            return;
        }

        $this->assertTrue(false);
    }

    public function testInvalidAggregateMethod()
    {
        try {
            Seller::orderByJoin('locationPrimary.id', 'asc', 'wrong')->get();
        } catch (InvalidAggregateMethod $e) {
            $this->assertEquals((new InvalidAggregateMethod())->message, $e->getMessage());

            return;
        }

        $this->assertTrue(false);
    }

    public function testOrderByInvalidDirection()
    {
        $this->expectException(InvalidDirection::class);
        Seller::orderByJoin('locationPrimary.id', ';DROP TABLE orders;--', 'test')->get();
    }
}


================================================
FILE: tests/Tests/JoinTypeTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class JoinTypeTest extends TestCase
{
    public function testLeftJoin()
    {
        Seller::setLeftJoin(true)->whereJoin('city.name', '=', 'test')->get();

        $queryTest = 'select sellers.*
            from "sellers"
            left join "cities"
            on "cities"."id" = "sellers"."city_id"
            and "cities"."deleted_at" is null
            where "cities"."name" = "test"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testInnerJoin()
    {
        Seller::setLeftJoin(false)->whereJoin('city.name', '=', 'test')->get();

        $queryTest = 'select sellers.*
            from "sellers"
            inner join "cities"
            on "cities"."id" = "sellers"."city_id"
            and "cities"."deleted_at" is null
            where "cities"."name" = "test"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testMixedJoin()
    {
        Order::joinRelations('seller', true)->joinRelations('seller.city', false)->joinRelations('seller.city.state', true)->get();

        $queryTest = 'select orders.* 
            from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            inner join "cities" on "cities"."id" = "sellers"."city_id" 
            and "cities"."deleted_at" is null 
            left join "states" on "states"."id" = "cities"."state_id" 
            and "states"."deleted_at" is null 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/KeysOwnerTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Key\Order;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Key\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class KeysOwnerTest extends TestCase
{
    public function testBelogsTo()
    {
        Order::joinRelations('sellerOwner')
            ->get();

        $queryTest = 'select key_orders.* 
            from "key_orders" 
            left join "key_sellers" on "key_sellers"."id_seller_owner" = "key_orders"."id_seller_foreign" 
            group by "key_orders"."id_order_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasOne()
    {
        Seller::joinRelations('locationOwner')
            ->get();

        $queryTest = 'select key_sellers.* 
            from "key_sellers" 
            left join "key_locations" on "key_locations"."id_seller_foreign" = "key_sellers"."id_seller_owner" 
            group by "key_sellers"."id_seller_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasMany()
    {
        Seller::joinRelations('locationsOwner')
            ->get();

        $queryTest = 'select key_sellers.* 
            from "key_sellers" 
            left join "key_locations" on "key_locations"."id_seller_foreign" = "key_sellers"."id_seller_owner" 
            group by "key_sellers"."id_seller_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/KeysTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Key\Order;
use Fico7489\Laravel\EloquentJoin\Tests\Models\Key\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class KeysTest extends TestCase
{
    public function testBelongsTo()
    {
        Order::joinRelations('seller')
            ->get();

        $queryTest = 'select key_orders.* 
            from "key_orders" 
            left join "key_sellers" on "key_sellers"."id_seller_primary" = "key_orders"."id_seller_foreign" 
            group by "key_orders"."id_order_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasOne()
    {
        Seller::joinRelations('location')
            ->get();

        $queryTest = 'select key_sellers.*
            from "key_sellers"
            left join "key_locations" on "key_locations"."id_seller_foreign" = "key_sellers"."id_seller_primary"
            group by "key_sellers"."id_seller_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasMany()
    {
        Seller::joinRelations('locations')
            ->get();

        $queryTest = 'select key_sellers.*
            from "key_sellers"
            left join "key_locations" on "key_locations"."id_seller_foreign" = "key_sellers"."id_seller_primary"
            group by "key_sellers"."id_seller_primary"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/OptionsTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\EloquentJoinBuilder;
use Fico7489\Laravel\EloquentJoin\Tests\Models\City;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class OptionsTest extends TestCase
{
    public function testUseTableAlias()
    {
        $city = new City();
        $this->assertEquals(false, $city->newModelQuery()->isUseTableAlias());
        $city->useTableAlias = true;
        $this->assertEquals(true, $city->newModelQuery()->isUseTableAlias());
    }

    public function testAppendRelationsCount()
    {
        $city = new City();
        $this->assertEquals(false, $city->newModelQuery()->isAppendRelationsCount());
        $city->appendRelationsCount = true;
        $this->assertEquals(true, $city->newModelQuery()->isAppendRelationsCount());
    }

    public function testLeftJoin()
    {
        $city = new City();
        $this->assertEquals(true, $city->newModelQuery()->isLeftJoin());
        $city->leftJoin = false;
        $this->assertEquals(false, $city->newModelQuery()->isLeftJoin());
    }

    public function testAggregateMethod()
    {
        $city = new City();
        $this->assertEquals(EloquentJoinBuilder::AGGREGATE_MAX, $city->newModelQuery()->getAggregateMethod());
        $city->aggregateMethod = EloquentJoinBuilder::AGGREGATE_MIN;
        $this->assertEquals(EloquentJoinBuilder::AGGREGATE_MIN, $city->newModelQuery()->getAggregateMethod());
    }
}


================================================
FILE: tests/Tests/Relations/BelongsToTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Relations;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Order;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class BelongsToTest extends TestCase
{
    public function testBelongsTo()
    {
        Order::joinRelations('seller')->get();

        $queryTest = 'select orders.* from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testBelongsToHasOne()
    {
        Order::joinRelations('seller.locationPrimary')->get();

        $queryTest = 'select orders.* from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            and "locations"."deleted_at" is null 
            where "orders"."deleted_at" is null
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testBelongsToHasMany()
    {
        Order::joinRelations('seller.locations')->get();

        $queryTest = 'select orders.* from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."deleted_at" is null 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testBelongsToHasOneHasMany()
    {
        Order::joinRelations('seller.locationPrimary.integrations')->get();

        $queryTest = 'select orders.* from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 and "locations"."deleted_at" is null 
            left join "integrations" on "integrations"."location_id" = "locations"."id" 
            and "integrations"."deleted_at" is null 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testBelongsToHasManyHasOne()
    {
        Order::joinRelations('seller.locationPrimary.locationAddressPrimary')->get();

        $queryTest = 'select orders.* from "orders" 
            left join "sellers" on "sellers"."id" = "orders"."seller_id" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 1 
            and "locations"."deleted_at" is null 
            left join "location_addresses" on "location_addresses"."location_id" = "locations"."id" 
            and "location_addresses"."is_primary" = 1 
            and "location_addresses"."deleted_at" is null 
            where "orders"."deleted_at" is null 
            group by "orders"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Relations/HasManyTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Relations;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class HasManyTest extends TestCase
{
    public function testHasMany()
    {
        Seller::joinRelations('locations')->get();

        $queryTest = 'select sellers.* 
            from "sellers" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasManyHasOne()
    {
        Seller::joinRelations('locations.city')->get();

        $queryTest = 'select sellers.* 
            from "sellers" left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."deleted_at" is null 
            left join "cities" on "cities"."id" = "locations"."city_id" 
            and "cities"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasManyBelongsTo()
    {
        Seller::joinRelations('locations.integrations')->get();

        $queryTest = 'select sellers.* 
            from "sellers" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."deleted_at" is null 
            left join "integrations" on "integrations"."location_id" = "locations"."id"
            and "integrations"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/Relations/HasOneTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests\Relations;

use Fico7489\Laravel\EloquentJoin\Tests\Models\Seller;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class HasOneTest extends TestCase
{
    public function testHasOne()
    {
        Seller::joinRelations('location')->get();

        $queryTest = 'select sellers.* 
            from "sellers" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 0
            and "locations"."is_secondary" = 0 
            and "locations"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasOneBelongsTo()
    {
        Seller::joinRelations('location.city')->get();

        $queryTest = 'select sellers.* 
            from "sellers" left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 0
            and "locations"."is_secondary" = 0 
            and "locations"."deleted_at" is null 
            left join "cities" on "cities"."id" = "locations"."city_id" 
            and "cities"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testHasOneHasMany()
    {
        Seller::joinRelations('location.integrations')->get();

        $queryTest = 'select sellers.* 
            from "sellers" 
            left join "locations" on "locations"."seller_id" = "sellers"."id" 
            and "locations"."is_primary" = 0
            and "locations"."is_secondary" = 0 
            and "locations"."deleted_at" is null 
            left join "integrations" on "integrations"."location_id" = "locations"."id"
            and "integrations"."deleted_at" is null 
            group by "sellers"."id"';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/Tests/SoftDeleteTest.php
================================================
<?php

namespace Fico7489\Laravel\EloquentJoin\Tests\Tests;

use Fico7489\Laravel\EloquentJoin\Tests\Models\OrderItem;
use Fico7489\Laravel\EloquentJoin\Tests\TestCase;

class SoftDeleteTest extends TestCase
{
    public function testNotRelatedWithoutTrashedDefault()
    {
        OrderItem::orderByJoin('name')->get();
        $queryTest = 'select * 
            from "order_items" 
            where "order_items"."deleted_at" is null 
            order by "order_items"."name" asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testNotRelatedWithoutTrashedExplicit()
    {
        OrderItem::orderByJoin('name')->withoutTrashed()->get();
        $queryTest = 'select * 
            from "order_items"
            where "order_items"."deleted_at" is null
            order by "order_items"."name" asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testNotRelatedOnlyTrashedExplicit()
    {
        OrderItem::orderByJoin('name')->onlyTrashed()->get();
        $queryTest = 'select * 
            from "order_items" 
            where "order_items"."deleted_at" is not null
            order by "order_items"."name" asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testNotRelatedWithTrashedExplicit()
    {
        OrderItem::orderByJoin('name')->withTrashed()->get();
        $queryTest = 'select * 
            from "order_items" 
            order by "order_items"."name" asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedWithoutTrashedDefault()
    {
        OrderItem::orderByJoin('order.number')->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items" left join "orders" 
            on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null
            where "order_items"."deleted_at" is null 
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedWithoutTrashedExplicit()
    {
        OrderItem::orderByJoin('order.number')->withoutTrashed()->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items" 
            left join "orders" 
            on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null 
            where "order_items"."deleted_at" is null 
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedOnlyTrashedExplicit()
    {
        OrderItem::orderByJoin('order.number')->onlyTrashed()->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items" 
            left join "orders" 
            on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null 
            where "order_items"."deleted_at" is not null
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedWithTrashedExplicit()
    {
        OrderItem::orderByJoin('order.number')->withTrashed()->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items" 
            left join "orders" 
            on "orders"."id" = "order_items"."order_id" 
            and "orders"."deleted_at" is null 
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedWithTrashedOnRelation()
    {
        OrderItem::orderByJoin('orderWithTrashed.number')->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items" 
            left join "orders" 
            on "orders"."id" = "order_items"."order_id" 
            where "order_items"."deleted_at" is null
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }

    public function testRelatedOnlyTrashedOnRelation()
    {
        OrderItem::orderByJoin('orderOnlyTrashed.number')->get();
        $queryTest = 'select order_items.*, MAX("orders"."number") as sort
            from "order_items"
            left join "orders" 
            on "orders"."id" = "order_items"."order_id"
            and "orders"."deleted_at" is not null 
            where "order_items"."deleted_at" is null 
            group by "order_items"."id"
            order by sort asc';

        $this->assertQueryMatches($queryTest, $this->fetchQuery());
    }
}


================================================
FILE: tests/database/migrations/2017_11_04_163552_create_database.php
================================================
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

/**
 * Class CreateDatabase.
 */
class CreateDatabase extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('states', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('cities', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();
            $table->unsignedInteger('state_id')->nullable();

            $table->foreign('state_id')->references('id')->on('states');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('sellers', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title')->nullable();
            $table->unsignedInteger('city_id')->nullable();

            $table->foreign('city_id')->references('id')->on('cities');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('number')->nullable();
            $table->unsignedInteger('seller_id')->nullable();

            $table->foreign('seller_id')->references('id')->on('sellers');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('order_items', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->unsignedInteger('order_id')->nullable();

            $table->foreign('order_id')->references('id')->on('orders');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('locations', function (Blueprint $table) {
            $table->increments('id');
            $table->string('address')->nullable();
            $table->boolean('is_primary')->default(0);
            $table->boolean('is_secondary')->default(0);
            $table->unsignedInteger('seller_id')->nullable();
            $table->unsignedInteger('city_id')->nullable();

            $table->foreign('seller_id')->references('id')->on('sellers');
            $table->foreign('city_id')->references('id')->on('cities');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('zip_codes', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();
            $table->boolean('is_primary')->default(0);
            $table->unsignedInteger('city_id')->nullable();

            $table->foreign('city_id')->references('id')->on('cities');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('location_addresses', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();
            $table->boolean('is_primary')->default(0);
            $table->unsignedInteger('location_id')->nullable();

            $table->foreign('location_id')->references('id')->on('locations');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();
            $table->unsignedInteger('location_address_id')->nullable();

            $table->foreign('location_address_id')->references('id')->on('location_addresses');

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('integrations', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name')->nullable();
            $table->unsignedInteger('location_id')->nullable();

            $table->foreign('location_id')->references('id')->on('locations');

            $table->timestamps();
            $table->softDeletes();
        });

        //for key tests
        Schema::create('key_orders', function (Blueprint $table) {
            $table->increments('id_order_primary');
            $table->unsignedInteger('id_order_owner')->nullable();

            $table->string('number')->nullable();

            $table->unsignedInteger('id_seller_foreign')->nullable();
            $table->foreign('id_seller_foreign')->references('id')->on('sellers');
        });

        Schema::create('key_sellers', function (Blueprint $table) {
            $table->increments('id_seller_primary');
            $table->unsignedInteger('id_seller_owner')->nullable();

            $table->string('title')->nullable();
        });

        Schema::create('key_locations', function (Blueprint $table) {
            $table->increments('id_location_primary');
            $table->unsignedInteger('id_location_owner')->nullable();

            $table->string('address')->nullable();

            $table->unsignedInteger('id_seller_foreign')->nullable();
            $table->foreign('id_seller_foreign')->references('id')->on('sellers');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::drop('users');
        Schema::drop('sellers');
        Schema::drop('order_items');
        Schema::drop('locations');
        Schema::drop('cities');
        Schema::drop('zip_codes');
        Schema::drop('states');
        Schema::drop('location_addresses');
        Schema::drop('integrations');
        Schema::drop('orders');

        //for key tests
        Schema::drop('key_orders');
        Schema::drop('key_sellers');
        Schema::drop('key_locations');
    }
}
Download .txt
gitextract_4l8jykqq/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── .php_cs
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│   ├── EloquentJoinBuilder.php
│   ├── Exceptions/
│   │   ├── InvalidAggregateMethod.php
│   │   ├── InvalidDirection.php
│   │   ├── InvalidRelation.php
│   │   ├── InvalidRelationClause.php
│   │   ├── InvalidRelationGlobalScope.php
│   │   └── InvalidRelationWhere.php
│   ├── Relations/
│   │   ├── BelongsToJoin.php
│   │   ├── HasManyJoin.php
│   │   └── HasOneJoin.php
│   └── Traits/
│       ├── EloquentJoin.php
│       ├── ExtendRelationsTrait.php
│       └── JoinRelationTrait.php
└── tests/
    ├── Models/
    │   ├── BaseModel.php
    │   ├── City.php
    │   ├── Integration.php
    │   ├── Key/
    │   │   ├── Location.php
    │   │   ├── Order.php
    │   │   └── Seller.php
    │   ├── Location.php
    │   ├── LocationAddress.php
    │   ├── LocationWithGlobalScope.php
    │   ├── Order.php
    │   ├── OrderItem.php
    │   ├── Seller.php
    │   ├── State.php
    │   ├── User.php
    │   └── ZipCode.php
    ├── Scope/
    │   └── TestExceptionScope.php
    ├── ServiceProvider.php
    ├── TestCase.php
    ├── Tests/
    │   ├── AggregateJoinTest.php
    │   ├── AppendRelationsCountTest.php
    │   ├── Clauses/
    │   │   ├── JoinRelationsTest.php
    │   │   ├── OrWhereInTest.php
    │   │   ├── OrWhereNotInTest.php
    │   │   ├── OrWhereTest.php
    │   │   ├── OrderByTest.php
    │   │   ├── WhereInTest.php
    │   │   ├── WhereNotInTest.php
    │   │   └── WhereTest.php
    │   ├── ClosureOnRelationTest.php
    │   ├── ClosureTest.php
    │   ├── ExceptionTest.php
    │   ├── JoinTypeTest.php
    │   ├── KeysOwnerTest.php
    │   ├── KeysTest.php
    │   ├── OptionsTest.php
    │   ├── Relations/
    │   │   ├── BelongsToTest.php
    │   │   ├── HasManyTest.php
    │   │   └── HasOneTest.php
    │   └── SoftDeleteTest.php
    └── database/
        └── migrations/
            └── 2017_11_04_163552_create_database.php
Download .txt
SYMBOL INDEX (183 symbols across 53 files)

FILE: src/EloquentJoinBuilder.php
  class EloquentJoinBuilder (line 19) | class EloquentJoinBuilder extends Builder
    method where (line 53) | public function where($column, $operator = null, $value = null, $boole...
    method whereJoin (line 70) | public function whereJoin($column, $operator, $value, $boolean = 'and')
    method orWhereJoin (line 78) | public function orWhereJoin($column, $operator, $value)
    method whereInJoin (line 86) | public function whereInJoin($column, $values, $boolean = 'and', $not =...
    method whereNotInJoin (line 94) | public function whereNotInJoin($column, $values, $boolean = 'and')
    method orWhereInJoin (line 102) | public function orWhereInJoin($column, $values)
    method orWhereNotInJoin (line 110) | public function orWhereNotInJoin($column, $values)
    method orderByJoin (line 118) | public function orderByJoin($column, $direction = 'asc', $aggregateMet...
    method joinRelations (line 155) | public function joinRelations($relations, $leftJoin = null)
    method performJoin (line 172) | protected function performJoin($relations, $leftJoin = null)
    method joinQuery (line 253) | protected function joinQuery($join, $relation, $relatedTableAlias)
    method applyClauseOnRelation (line 277) | private function applyClauseOnRelation(JoinClause $join, string $metho...
    method checkAggregateMethod (line 309) | private function checkAggregateMethod($aggregateMethod)
    method checkDirection (line 322) | private function checkDirection($direction)
    method isUseTableAlias (line 330) | public function isUseTableAlias(): bool
    method setUseTableAlias (line 335) | public function setUseTableAlias(bool $useTableAlias)
    method isLeftJoin (line 342) | public function isLeftJoin(): bool
    method setLeftJoin (line 347) | public function setLeftJoin(bool $leftJoin)
    method isAppendRelationsCount (line 354) | public function isAppendRelationsCount(): bool
    method setAppendRelationsCount (line 359) | public function setAppendRelationsCount(bool $appendRelationsCount)
    method getAggregateMethod (line 366) | public function getAggregateMethod(): string
    method setAggregateMethod (line 371) | public function setAggregateMethod(string $aggregateMethod)

FILE: src/Exceptions/InvalidAggregateMethod.php
  class InvalidAggregateMethod (line 5) | class InvalidAggregateMethod extends \Exception

FILE: src/Exceptions/InvalidDirection.php
  class InvalidDirection (line 5) | class InvalidDirection extends \Exception

FILE: src/Exceptions/InvalidRelation.php
  class InvalidRelation (line 5) | class InvalidRelation extends \Exception

FILE: src/Exceptions/InvalidRelationClause.php
  class InvalidRelationClause (line 5) | class InvalidRelationClause extends \Exception

FILE: src/Exceptions/InvalidRelationGlobalScope.php
  class InvalidRelationGlobalScope (line 5) | class InvalidRelationGlobalScope extends \Exception

FILE: src/Exceptions/InvalidRelationWhere.php
  class InvalidRelationWhere (line 5) | class InvalidRelationWhere extends \Exception

FILE: src/Relations/BelongsToJoin.php
  class BelongsToJoin (line 8) | class BelongsToJoin extends BelongsTo

FILE: src/Relations/HasManyJoin.php
  class HasManyJoin (line 8) | class HasManyJoin extends HasMany

FILE: src/Relations/HasOneJoin.php
  class HasOneJoin (line 8) | class HasOneJoin extends HasOne

FILE: src/Traits/EloquentJoin.php
  type EloquentJoin (line 19) | trait EloquentJoin
    method newEloquentBuilder (line 28) | public function newEloquentBuilder($query)

FILE: src/Traits/ExtendRelationsTrait.php
  type ExtendRelationsTrait (line 11) | trait ExtendRelationsTrait
    method newBelongsTo (line 13) | protected function newBelongsTo(Builder $query, Model $child, $foreign...
    method newHasOne (line 18) | protected function newHasOne(Builder $query, Model $parent, $foreignKe...
    method newHasMany (line 23) | protected function newHasMany(Builder $query, Model $parent, $foreignK...

FILE: src/Traits/JoinRelationTrait.php
  type JoinRelationTrait (line 7) | trait JoinRelationTrait
    method __call (line 17) | public function __call($method, $parameters)

FILE: tests/Models/BaseModel.php
  class BaseModel (line 8) | class BaseModel extends Model

FILE: tests/Models/City.php
  class City (line 7) | class City extends BaseModel
    method state (line 15) | public function state()
    method zipCodePrimary (line 20) | public function zipCodePrimary()
    method sellers (line 26) | public function sellers()
    method zipCodes (line 31) | public function zipCodes()

FILE: tests/Models/Integration.php
  class Integration (line 7) | class Integration extends BaseModel

FILE: tests/Models/Key/Location.php
  class Location (line 7) | class Location extends BaseModel

FILE: tests/Models/Key/Order.php
  class Order (line 7) | class Order extends BaseModel
    method seller (line 15) | public function seller()
    method sellerOwner (line 20) | public function sellerOwner()

FILE: tests/Models/Key/Seller.php
  class Seller (line 7) | class Seller extends BaseModel
    method location (line 15) | public function location()
    method locations (line 20) | public function locations()
    method locationOwner (line 25) | public function locationOwner()
    method locationsOwner (line 30) | public function locationsOwner()

FILE: tests/Models/Location.php
  class Location (line 7) | class Location extends BaseModel
    method seller (line 15) | public function seller()
    method city (line 20) | public function city()
    method locationAddressPrimary (line 25) | public function locationAddressPrimary()
    method integrations (line 31) | public function integrations()

FILE: tests/Models/LocationAddress.php
  class LocationAddress (line 7) | class LocationAddress extends BaseModel
    method users (line 15) | public function users()

FILE: tests/Models/LocationWithGlobalScope.php
  class LocationWithGlobalScope (line 8) | class LocationWithGlobalScope extends BaseModel
    method boot (line 14) | protected static function boot()

FILE: tests/Models/Order.php
  class Order (line 7) | class Order extends BaseModel
    method seller (line 15) | public function seller()

FILE: tests/Models/OrderItem.php
  class OrderItem (line 7) | class OrderItem extends BaseModel
    method order (line 15) | public function order()
    method orderWithTrashed (line 20) | public function orderWithTrashed()
    method orderOnlyTrashed (line 26) | public function orderOnlyTrashed()

FILE: tests/Models/Seller.php
  class Seller (line 5) | class Seller extends BaseModel
    method location (line 11) | public function location()
    method locations (line 18) | public function locations()
    method locationPrimary (line 23) | public function locationPrimary()
    method locationPrimaryInvalid (line 29) | public function locationPrimaryInvalid()
    method locationPrimaryInvalid2 (line 36) | public function locationPrimaryInvalid2()
    method locationPrimaryInvalid3 (line 44) | public function locationPrimaryInvalid3()
    method locationSecondary (line 49) | public function locationSecondary()
    method locationPrimaryOrSecondary (line 55) | public function locationPrimaryOrSecondary()
    method city (line 62) | public function city()

FILE: tests/Models/State.php
  class State (line 7) | class State extends BaseModel
    method cities (line 15) | public function cities()

FILE: tests/Models/User.php
  class User (line 7) | class User extends BaseModel

FILE: tests/Models/ZipCode.php
  class ZipCode (line 7) | class ZipCode extends BaseModel

FILE: tests/Scope/TestExceptionScope.php
  class TestExceptionScope (line 9) | class TestExceptionScope implements Scope
    method apply (line 11) | public function apply(Builder $builder, Model $model)

FILE: tests/ServiceProvider.php
  class ServiceProvider (line 5) | class ServiceProvider extends \Illuminate\Support\ServiceProvider
    method register (line 7) | public function register()
    method boot (line 12) | public function boot()
    method loadMigrationsFrom (line 17) | protected function loadMigrationsFrom($path)

FILE: tests/TestCase.php
  class TestCase (line 10) | abstract class TestCase extends \Orchestra\Testbench\TestCase
    method setUp (line 12) | public function setUp(): void
    method startListening (line 40) | protected function startListening()
    method fetchLastLog (line 45) | protected function fetchLastLog()
    method fetchQuery (line 52) | protected function fetchQuery()
    method fetchBindings (line 65) | protected function fetchBindings()
    method getEnvironmentSetUp (line 70) | protected function getEnvironmentSetUp($app)
    method getPackageProviders (line 105) | protected function getPackageProviders($app)
    method assertQueryMatches (line 110) | protected function assertQueryMatches($expected, $actual)

FILE: tests/Tests/AggregateJoinTest.php
  class AggregateJoinTest (line 9) | class AggregateJoinTest extends TestCase
    method testAvg (line 18) | public function testAvg()
    method testSum (line 28) | public function testSum()
    method testMax (line 38) | public function testMax()
    method testMin (line 48) | public function testMin()
    method testCount (line 58) | public function testCount()

FILE: tests/Tests/AppendRelationsCountTest.php
  class AppendRelationsCountTest (line 8) | class AppendRelationsCountTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/JoinRelationsTest.php
  class JoinRelationsTest (line 8) | class JoinRelationsTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/OrWhereInTest.php
  class OrWhereInTest (line 8) | class OrWhereInTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/OrWhereNotInTest.php
  class OrWhereNotInTest (line 8) | class OrWhereNotInTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/OrWhereTest.php
  class OrWhereTest (line 8) | class OrWhereTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/OrderByTest.php
  class OrderByTest (line 8) | class OrderByTest extends TestCase
    method testOrderBy (line 10) | public function testOrderBy()
    method testOrderByMultiple (line 26) | public function testOrderByMultiple()

FILE: tests/Tests/Clauses/WhereInTest.php
  class WhereInTest (line 8) | class WhereInTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/WhereNotInTest.php
  class WhereNotInTest (line 8) | class WhereNotInTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/Clauses/WhereTest.php
  class WhereTest (line 8) | class WhereTest extends TestCase
    method testWhere (line 10) | public function testWhere()

FILE: tests/Tests/ClosureOnRelationTest.php
  class ClosureOnRelationTest (line 8) | class ClosureOnRelationTest extends TestCase
    method testWhereOnRelationWithOrderByJoin (line 10) | public function testWhereOnRelationWithOrderByJoin()
    method testWhereOnRelationWithoutOrderByJoin (line 63) | public function testWhereOnRelationWithoutOrderByJoin()

FILE: tests/Tests/ClosureTest.php
  class ClosureTest (line 8) | class ClosureTest extends TestCase
    method testNestOne (line 10) | public function testNestOne()
    method testNestTwo (line 28) | public function testNestTwo()

FILE: tests/Tests/ExceptionTest.php
  class ExceptionTest (line 15) | class ExceptionTest extends TestCase
    method testInvalidRelation (line 17) | public function testInvalidRelation()
    method testInvalidRelationWhere (line 30) | public function testInvalidRelationWhere()
    method testInvalidRelationClause (line 43) | public function testInvalidRelationClause()
    method testInvalidRelationGlobalScope (line 56) | public function testInvalidRelationGlobalScope()
    method testInvalidAggregateMethod (line 69) | public function testInvalidAggregateMethod()
    method testOrderByInvalidDirection (line 82) | public function testOrderByInvalidDirection()

FILE: tests/Tests/JoinTypeTest.php
  class JoinTypeTest (line 9) | class JoinTypeTest extends TestCase
    method testLeftJoin (line 11) | public function testLeftJoin()
    method testInnerJoin (line 25) | public function testInnerJoin()
    method testMixedJoin (line 39) | public function testMixedJoin()

FILE: tests/Tests/KeysOwnerTest.php
  class KeysOwnerTest (line 9) | class KeysOwnerTest extends TestCase
    method testBelogsTo (line 11) | public function testBelogsTo()
    method testHasOne (line 24) | public function testHasOne()
    method testHasMany (line 37) | public function testHasMany()

FILE: tests/Tests/KeysTest.php
  class KeysTest (line 9) | class KeysTest extends TestCase
    method testBelongsTo (line 11) | public function testBelongsTo()
    method testHasOne (line 24) | public function testHasOne()
    method testHasMany (line 37) | public function testHasMany()

FILE: tests/Tests/OptionsTest.php
  class OptionsTest (line 9) | class OptionsTest extends TestCase
    method testUseTableAlias (line 11) | public function testUseTableAlias()
    method testAppendRelationsCount (line 19) | public function testAppendRelationsCount()
    method testLeftJoin (line 27) | public function testLeftJoin()
    method testAggregateMethod (line 35) | public function testAggregateMethod()

FILE: tests/Tests/Relations/BelongsToTest.php
  class BelongsToTest (line 8) | class BelongsToTest extends TestCase
    method testBelongsTo (line 10) | public function testBelongsTo()
    method testBelongsToHasOne (line 22) | public function testBelongsToHasOne()
    method testBelongsToHasMany (line 37) | public function testBelongsToHasMany()
    method testBelongsToHasOneHasMany (line 51) | public function testBelongsToHasOneHasMany()
    method testBelongsToHasManyHasOne (line 67) | public function testBelongsToHasManyHasOne()

FILE: tests/Tests/Relations/HasManyTest.php
  class HasManyTest (line 8) | class HasManyTest extends TestCase
    method testHasMany (line 10) | public function testHasMany()
    method testHasManyHasOne (line 23) | public function testHasManyHasOne()
    method testHasManyBelongsTo (line 37) | public function testHasManyBelongsTo()

FILE: tests/Tests/Relations/HasOneTest.php
  class HasOneTest (line 8) | class HasOneTest extends TestCase
    method testHasOne (line 10) | public function testHasOne()
    method testHasOneBelongsTo (line 25) | public function testHasOneBelongsTo()
    method testHasOneHasMany (line 41) | public function testHasOneHasMany()

FILE: tests/Tests/SoftDeleteTest.php
  class SoftDeleteTest (line 8) | class SoftDeleteTest extends TestCase
    method testNotRelatedWithoutTrashedDefault (line 10) | public function testNotRelatedWithoutTrashedDefault()
    method testNotRelatedWithoutTrashedExplicit (line 21) | public function testNotRelatedWithoutTrashedExplicit()
    method testNotRelatedOnlyTrashedExplicit (line 32) | public function testNotRelatedOnlyTrashedExplicit()
    method testNotRelatedWithTrashedExplicit (line 43) | public function testNotRelatedWithTrashedExplicit()
    method testRelatedWithoutTrashedDefault (line 53) | public function testRelatedWithoutTrashedDefault()
    method testRelatedWithoutTrashedExplicit (line 67) | public function testRelatedWithoutTrashedExplicit()
    method testRelatedOnlyTrashedExplicit (line 82) | public function testRelatedOnlyTrashedExplicit()
    method testRelatedWithTrashedExplicit (line 97) | public function testRelatedWithTrashedExplicit()
    method testRelatedWithTrashedOnRelation (line 111) | public function testRelatedWithTrashedOnRelation()
    method testRelatedOnlyTrashedOnRelation (line 125) | public function testRelatedOnlyTrashedOnRelation()

FILE: tests/database/migrations/2017_11_04_163552_create_database.php
  class CreateDatabase (line 9) | class CreateDatabase extends Migration
    method up (line 14) | public function up()
    method down (line 161) | public function down()
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (101K chars).
[
  {
    "path": ".github/workflows/test.yml",
    "chars": 1064,
    "preview": "name: Test\n\non:\n  pull_request:\n  push:\n    branches:\n      - master\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strat"
  },
  {
    "path": ".gitignore",
    "chars": 65,
    "preview": "/vendor\n/.idea\ncomposer.lock\n.php_cs.cache\n.phpunit.result.cache\n"
  },
  {
    "path": ".php_cs",
    "chars": 387,
    "preview": "<?php\n\nuse PhpCsFixer\\Config;\nuse PhpCsFixer\\Finder;\n\n/*\n * Define folders to fix\n */\n$finder = Finder::create()\n    ->i"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2017 Filip Horvat\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 13913,
    "preview": "![Tests status](https://github.com/fico7489/laravel-eloquent-join/workflows/Test/badge.svg)\n\n# Laravel Eloquent Join\n\nTh"
  },
  {
    "path": "composer.json",
    "chars": 1304,
    "preview": "{\n    \"name\": \"fico7489/laravel-eloquent-join\",\n    \"description\": \"This package introduces the join magic for eloquent "
  },
  {
    "path": "phpunit.xml",
    "chars": 1008,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNa"
  },
  {
    "path": "src/EloquentJoinBuilder.php",
    "chars": 13301,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Exceptions\\InvalidAggregateMethod;\nus"
  },
  {
    "path": "src/Exceptions/InvalidAggregateMethod.php",
    "chars": 162,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidAggregateMethod extends \\Exception\n{\n    public"
  },
  {
    "path": "src/Exceptions/InvalidDirection.php",
    "chars": 201,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidDirection extends \\Exception\n{\n    public $mess"
  },
  {
    "path": "src/Exceptions/InvalidRelation.php",
    "chars": 203,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidRelation extends \\Exception\n{\n    public $messa"
  },
  {
    "path": "src/Exceptions/InvalidRelationClause.php",
    "chars": 249,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidRelationClause extends \\Exception\n{\n    public "
  },
  {
    "path": "src/Exceptions/InvalidRelationGlobalScope.php",
    "chars": 193,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidRelationGlobalScope extends \\Exception\n{\n    pu"
  },
  {
    "path": "src/Exceptions/InvalidRelationWhere.php",
    "chars": 277,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Exceptions;\n\nclass InvalidRelationWhere extends \\Exception\n{\n    public $"
  },
  {
    "path": "src/Relations/BelongsToJoin.php",
    "chars": 243,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Traits\\JoinRelationTrait;\nu"
  },
  {
    "path": "src/Relations/HasManyJoin.php",
    "chars": 237,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Traits\\JoinRelationTrait;\nu"
  },
  {
    "path": "src/Relations/HasOneJoin.php",
    "chars": 234,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Traits\\JoinRelationTrait;\nu"
  },
  {
    "path": "src/Traits/EloquentJoin.php",
    "chars": 1648,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Traits;\n\nuse Fico7489\\Laravel\\EloquentJoin\\EloquentJoinBuilder;\n\n/**\n * T"
  },
  {
    "path": "src/Traits/ExtendRelationsTrait.php",
    "chars": 888,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Traits;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Relations\\BelongsToJoin;\nuse F"
  },
  {
    "path": "src/Traits/JoinRelationTrait.php",
    "chars": 575,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Traits;\n\nuse Fico7489\\Laravel\\EloquentJoin\\EloquentJoinBuilder;\n\ntrait Jo"
  },
  {
    "path": "tests/Models/BaseModel.php",
    "chars": 214,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Traits\\EloquentJoin;\nuse"
  },
  {
    "path": "tests/Models/City.php",
    "chars": 683,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass City "
  },
  {
    "path": "tests/Models/Integration.php",
    "chars": 247,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass Integ"
  },
  {
    "path": "tests/Models/Key/Location.php",
    "chars": 315,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Key;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\BaseMod"
  },
  {
    "path": "tests/Models/Key/Order.php",
    "chars": 572,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Key;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\BaseMod"
  },
  {
    "path": "tests/Models/Key/Seller.php",
    "chars": 828,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Key;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\BaseMod"
  },
  {
    "path": "tests/Models/Location.php",
    "chars": 727,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass Locat"
  },
  {
    "path": "tests/Models/LocationAddress.php",
    "chars": 359,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass Locat"
  },
  {
    "path": "tests/Models/LocationWithGlobalScope.php",
    "chars": 418,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Scope\\TestExceptio"
  },
  {
    "path": "tests/Models/Order.php",
    "chars": 340,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass Order"
  },
  {
    "path": "tests/Models/OrderItem.php",
    "chars": 622,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass Order"
  },
  {
    "path": "tests/Models/Seller.php",
    "chars": 1536,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nclass Seller extends BaseModel\n{\n    protected $table = 's"
  },
  {
    "path": "tests/Models/State.php",
    "chars": 321,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass State"
  },
  {
    "path": "tests/Models/User.php",
    "chars": 233,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass User "
  },
  {
    "path": "tests/Models/ZipCode.php",
    "chars": 254,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Models;\n\nuse Illuminate\\Database\\Eloquent\\SoftDeletes;\n\nclass ZipCo"
  },
  {
    "path": "tests/Scope/TestExceptionScope.php",
    "chars": 346,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Scope;\n\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Da"
  },
  {
    "path": "tests/ServiceProvider.php",
    "chars": 566,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests;\n\nclass ServiceProvider extends \\Illuminate\\Support\\ServiceProvider"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 4025,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Location;\nuse Fico"
  },
  {
    "path": "tests/Tests/AggregateJoinTest.php",
    "chars": 2460,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\EloquentJoinBuild"
  },
  {
    "path": "tests/Tests/AppendRelationsCountTest.php",
    "chars": 1231,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/JoinRelationsTest.php",
    "chars": 619,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/OrWhereInTest.php",
    "chars": 816,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/OrWhereNotInTest.php",
    "chars": 826,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/OrWhereTest.php",
    "chars": 765,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/OrderByTest.php",
    "chars": 1329,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/WhereInTest.php",
    "chars": 701,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/WhereNotInTest.php",
    "chars": 711,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/Clauses/WhereTest.php",
    "chars": 692,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Clauses;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Orde"
  },
  {
    "path": "tests/Tests/ClosureOnRelationTest.php",
    "chars": 3517,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Seller;\nuse "
  },
  {
    "path": "tests/Tests/ClosureTest.php",
    "chars": 1870,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\OrderItem;\nu"
  },
  {
    "path": "tests/Tests/ExceptionTest.php",
    "chars": 2642,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Exceptions\\InvalidAggrega"
  },
  {
    "path": "tests/Tests/JoinTypeTest.php",
    "chars": 1849,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Order;\nuse F"
  },
  {
    "path": "tests/Tests/KeysOwnerTest.php",
    "chars": 1548,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Key\\Order;\nu"
  },
  {
    "path": "tests/Tests/KeysTest.php",
    "chars": 1529,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Key\\Order;\nu"
  },
  {
    "path": "tests/Tests/OptionsTest.php",
    "chars": 1466,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\EloquentJoinBuilder;\nuse "
  },
  {
    "path": "tests/Tests/Relations/BelongsToTest.php",
    "chars": 3241,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Or"
  },
  {
    "path": "tests/Tests/Relations/HasManyTest.php",
    "chars": 1705,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Se"
  },
  {
    "path": "tests/Tests/Relations/HasOneTest.php",
    "chars": 1978,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests\\Relations;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\Se"
  },
  {
    "path": "tests/Tests/SoftDeleteTest.php",
    "chars": 4937,
    "preview": "<?php\n\nnamespace Fico7489\\Laravel\\EloquentJoin\\Tests\\Tests;\n\nuse Fico7489\\Laravel\\EloquentJoin\\Tests\\Models\\OrderItem;\nu"
  },
  {
    "path": "tests/database/migrations/2017_11_04_163552_create_database.php",
    "chars": 5884,
    "preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\n\n/**\n * Class CreateDatab"
  }
]

About this extraction

This page contains the full source code of the fico7489/laravel-eloquent-join GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (90.0 KB), approximately 23.8k tokens, and a symbol index with 183 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!