• Stars
    star
    205
  • Rank 191,264 (Top 4 %)
  • Language
    PHP
  • Created almost 7 years ago
  • Updated almost 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Source code for my example app used in my Laracon Online 2018 talk

Laracon Online 2018

Requirement 1: Sort customers by name (last name, first name)

$customers = Customer::orderBy('last_name')->orderBy('first_name')->paginate();

Using scope instead:

$customers = Customer::orderByName()->paginate();
public function scopeOrderByName($query)
{
    $query->orderBy('last_name')->orderBy('first_name');
}

Requirement 2: Add company name

<th>Company</th>
    <td>{{ $customer->company->name }}</td>

Eager load companies:

$customers = Customer::with('company')->orderByName()->paginate();

Requirement 3: Add last interaction date

Via relationship:

<th>Last Interaction</th>
    <td>{{ $customer->interactions->sortByDesc('created_at')->first()->created_at->diffForHumans() }}</td>
$customers = Customer::with('company', 'interactions')->orderByName()->paginate();

Via database query:

$customers = Customer::with('company')->orderByName()->paginate();
<td>{{ $customer->interactions()->latest()->first()->created_at->diffForHumans() }}</td>

Via sub query:

public function scopeWithLastInteractionDate($query)
{
    $subQuery = \DB::table('interactions')
        ->select('created_at')
        ->whereRaw('customer_id = customers.id')
        ->latest()
        ->limit(1);

    return $query->select('customers.*')->selectSub($subQuery, 'last_interaction_date');
}
$customers = Customer::with('company')
    ->withLastInteractionDate()
    ->orderByName()
    ->paginate();
<td>{{ $customer->last_interaction_date->diffForHumans() }}</td>
protected $casts = [
    'birth_date' => 'date',
    'last_interaction_date' => 'datetime',
];

Via sub query (improved):

public function scopeWithLastInteractionDate($query)
{
    $query->addSubSelect('last_interaction_date', Interaction::select('created_at')
        ->whereRaw('customer_id = customers.id')
        ->latest()
    );
}
use Illuminate\Database\Eloquent\Builder;

Builder::macro('addSubSelect', function ($column, $query) {
    if (is_null($this->getQuery()->columns)) {
        $this->select($this->getQuery()->from.'.*');
    }

    return $this->selectSub($query->limit(1)->getQuery(), $column);
});

Requirement 4: Add last interaction type

<td>
    {{ $customer->last_interaction_date->diffForHumans() }}
    <span class="text-secondary">({{ $customer->last_interaction_type }})</span>
</td>
public function scopeWithLastInteractionType($query)
{
    $query->addSubSelect('last_interaction_type', Interaction::select('type')
        ->whereRaw('customer_id = customers.id')
        ->latest()
    );
}
$customers = Customer::with('company')
    ->withLastInteractionDate()
    ->withLastInteractionType()
    ->orderByName()
    ->paginate();

Sub query relationship approach:

$customers = Customer::with('company')
    ->withLastInteraction()
    ->orderByName()
    ->paginate();
public function lastInteraction()
{
    return $this->hasOne(Interaction::class, 'id', 'last_interaction_id');
}

public function scopeWithLastInteraction($query)
{
    $query->addSubSelect('last_interaction_id', Interaction::select('id')
        ->whereRaw('customer_id = customers.id')
        ->latest()
    )->with('lastInteraction');
}
<td>
    {{ $customer->lastInteraction->created_at->diffForHumans() }}
    <span class="text-secondary">({{ $customer->lastInteraction->type }})</span>
</td>

Remove last_interaction_date cast:

protected $casts = [
    'birth_date' => 'date',
];

Requirement 5: Make all columns sortable

<th><a class="{{ request('order', 'name') === 'name' ? 'text-dark' : '' }}" href="{{ route('customers', ['order' => 'name'] + request()->except('page')) }}">Name</a></th>
<th><a class="{{ request('order') === 'company' ? 'text-dark' : '' }}" href="{{ route('customers', ['order' => 'company'] + request()->except('page')) }}">Company</a></th>
<th><a class="{{ request('order') === 'birthday' ? 'text-dark' : '' }}" href="{{ route('customers', ['order' => 'birthday'] + request()->except('page')) }}">Birthday</a></th>
<th><a class="{{ request('order') === 'last_interaction' ? 'text-dark' : '' }}" href="{{ route('customers', ['order' => 'last_interaction'] + request()->except('page')) }}">Last Interaction</a></th>
$customers = Customer::with('company')
    ->withLastInteraction()
    ->orderByField($request->get('order', 'name'))
    ->paginate();
public function scopeOrderByField($query, $field)
{
    if ($field === 'name') {
        $query->orderByName();
    } elseif ($field === 'company') {
        $query->orderByCompany();
    } elseif ($field === 'birthday') {
        $query->orderByBirthday();
    } elseif ($field === 'last_interaction') {
        $query->orderByLastInteractionDate();
    }
}

Company, one approach:

public function scopeOrderByCompany($query)
{
    $query->join('companies', 'companies.id', '=', 'customers.company_id')->orderBy('companies.name');
}

Company, another approach:

public function scopeOrderByCompany($query)
{
    $query->orderBySub(Company::select('name')->whereRaw('customers.company_id = companies.id'));
}
Builder::macro('orderBySub', function ($query, $direction = 'asc') {
    return $this->orderByRaw("({$query->limit(1)->toSql()}) {$direction}");
});

Builder::macro('orderBySubDesc', function ($query) {
    return $this->orderBySub($query, 'desc');
});

Birthday:

public function scopeOrderByBirthday($query)
{
    $query->orderbyRaw("to_char(birth_date, 'MMDD')");
}

Last interaction date:

public function scopeOrderByLastInteractionDate($query)
{
    $query->orderBySubDesc(Interaction::select('created_at')->whereRaw('customers.id = interactions.customer_id')->latest());
}

Requirement 6: Add text based search

<form class="input-group my-4" action="{{ route('customers') }}" method="get">
    <input type="hidden" name="order" value="{{ request('order') }}">
    <input type="text" class="w-50 form-control" placeholder="Search..." name="search" value="{{ request('search') }}">
    <div class="input-group-append">
        <button class="btn btn-primary" type="submit">Search</button>
    </div>
</form>
$customers = Customer::with('company')
    ->withLastInteraction()
    ->whereSearch($request->get('search'))
    ->orderByField($request->get('order', 'name'))
    ->paginate();
public function scopeWhereSearch($query, $search)
{
    foreach (explode(' ', $search) as $term) {
        $query->where(function ($query) use ($term) {
            $query->where('first_name', 'ilike', '%'.$term.'%')
               ->orWhere('last_name', 'ilike', '%'.$term.'%')
               ->orWhereHas('company', function ($query) use ($term) {
                   $query->where('name', 'ilike', '%'.$term.'%');
               });
        });
    }
}

Requirement 7: Add filter for customers with birthday's this week

<select name="filter" class="custom-select">
    <option value="" selected>Filters...</option>
    <option value="birthday_this_week" {{ request('filter') === 'birthday_this_week' ? 'selected' : '' }}>Birthday this week</option>
</select>
$customers = Customer::with('company')
    ->withLastInteraction()
    ->whereFilters($request->only(['search', 'filter']))
    ->orderByField($request->get('order', 'name'))
    ->paginate();
public function scopeWhereFilters($query, array $filters)
{
    $filters = collect($filters);

    $query->when($filters->get('search'), function ($query, $search) {
        $query->whereSearch($search);
    })->when($filters->get('filter') === 'birthday_this_week', function ($query, $filter) {
        $query->whereBirthdayThisWeek();
    });
}
use Illuminate\Support\Carbon;

public function scopeWhereBirthdayThisWeek($query)
{
    $start = Carbon::now()->startOfWeek();
    $end = Carbon::now()->endOfWeek();

    $dates = collect(new \DatePeriod($start, new \DateInterval('P1D'), $end))->map(function ($date) {
        return $date->format('md');
    });

    return $query->whereNotNull('birth_date')->whereIn(\DB::raw("to_char(birth_date, 'MMDD')"), $dates);
}

Requirement 8: Limit results to the current user's access

public function scopeVisibleTo($query, User $user)
{
    if ($user->is_admin) {
        return $query;
    }

    return $query->where('sales_rep_id', $user->id);
}
use App\User;

$customers = Customer::with('company')
    ->withLastInteraction()
    ->whereFilters($request->only(['search', 'filter']))
    ->orderByField($request->get('order', 'name'))
    ->visibleTo(
        User::where('name', 'Jonathan Reinink')->first()
        // User::where('name', 'Taylor Otwell')->first()
        // User::where('name', 'Ian Landsman')->first()
    )
    ->paginate();