I wanna filter my table data. I have a table for states, one for cities and one for students.
states(id, name)
cities(id, name, state_id)
students(id, first_name, last_name, city_id)
When I wanna filter data with eloquent builder how can I access relationships for handle state filter.
<?php
namespace App\StudentSearch\Filters;
use Illuminate\Database\Eloquent\Builder;
class StateId implements Filter
{
/**
* #inheritDoc
*/
public static function apply(Builder $builder, $value)
{
// something like this
return $builder->where('state_id', $value);
}
}
StudentSearch.php
<?php
namespace App\StudentSearch;
use App\Student;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
class StudentSearch
{
public static function apply(Request $filters)
{
$query = static::applyDecoratorsFromRequest($filters, (new Student)->newQuery());
return static::getResults($query);
}
private static function applyDecoratorsFromRequest(Request $request, Builder $query)
{
foreach ($request->all() as $filterName => $value) {
$decorator = static::createFilterDecorator($filterName);
if (static::isValidDecorator($decorator)) {
$query = $decorator::apply($query, $value);
}
}
return $query;
}
private static function createFilterDecorator($name)
{
return __NAMESPACE__ . '\\Filters\\' . Str::studly($name);
}
private static function isValidDecorator($decorator)
{
return class_exists($decorator);
}
private static function getResults(Builder $query)
{
return $query->get();
}
}
Note that relations between tables is completely defined in models.
I found the solution.
We can do this with eloquent builder:
$builder->whereHas('relationName', function($query) use($value) {
$query->where('state_id', $value);
});
Link to source
Related
I do not understand how to properly connect the model and filter. I implemented this using Dependency Injection in the controller, but I don’t like the fact that it needs to be done in every method where filtering should be applied. It would be very convenient if the model itself understood which class with filters to use.
Tell me how to do better.
Filtration Class:
namespace App\Classes\Filter;
class QueryFilter
{
protected $query;
protected $params;
public function apply($query, $params)
{
$this->query = $query;
$this->params = $params;
foreach ($this->filters() as $filter => $value){
if(method_exists($this, $filter)){
$this->$filter($value);
}
}
return $this->query;
}
public function filters()
{
return $this->params;
}
}
Heirs implement filters for different models:
namespace App\Classes\Filter;
class PositionFilter extends QueryFilter
{
public function title($value)
{
$this->query->where('title', 'LIKE', "%$value%");
}
}
class GasStationFilter extends QueryFilter
{
public function number($value)
{
$this->query->where('number', 'LIKE', "%$value%");
}
public function region($value)
{
$this->query->whereHas('region', function ($query) use ($value){
$query->where('regions.id', $value);
});
}
}
In the controller, I inject the desired class with filters and apply filtering like this (I use scope in the model):
public function index(GasStationIndexRequest $request, GasStationFilter $filters)
{
$gasStations = GasStation::with('region')
->filter($request->validated(), $filters)
->take(10)
->get();
return GasStationSelect2Resource::collection($gasStations);
}
Model:
namespace App\Models;
class GasStation extends ListModel
{
public function region(): BelongsTo
{
return $this->belongsTo(Region::class);
}
public function scopeFilter($query, $params, $filters) : Builder
{
return $filters->apply($query, $params);
}
}
I created this code to work with laravel 5.8 to access a database with many foreign keys, seems like I am subverting the foreign keys, am I missing something, was hoping someone could point me in the right direction.
It works, but I think I am overdoing it and missing some eloquent shortcuts.
namespace App\Models\Entities;
use Illuminate\Database\Eloquent\Model;
class AbstractModel extends Model
{
public function isDuplicate(Model $model) {
return $model::where($model->getAttributes())->first();
}
}
///
namespace App\Models\Entities;
abstract class AbstractForeignModel extends AbstractModel {
public $timestamps = false;
public $fillable = ['value'];
public function store($value){
$foreign = $this->newInstance();
$foreign->value = $value;
if(!$this->isDuplicate($foreign)){
$foreign->save();
}
return $foreign->getId($value);
}
public function setValueAttribute($value){
$this->attributes['value'] = $value;
}
public function getId($value){
$result = self::where('value', $value)->first();
if($result){
return $result->id;
}
}
public function getValue($id){
$result = self::where('id', $id)->first();
if($result){
return $result->value;
}
}
}
///
namespace App\Models\Entities\Video;
use App\Models\Entities\AbstractForeignModel;
class ForeignModel extends AbstractForeignModel {
public function video() {
return $this->belongsTo('App\Models\Entities\Video');
}
}
Author, Description, Source, Title extend the above as empty classes
use App\Models\Entities\Video\Author;
use App\Models\Entities\Video\Description;
use App\Models\Entities\Video\Source;
use App\Models\Entities\Video\Title;
use Carbon\Carbon;
class Video extends AbstractModel {
protected $fillable = ['author_id', 'title_id', 'description_id',
'source_id', 'published_at'];
public function store($data) {
$video = new Video;
$video->author_id = $data->author;
$video->title_id = $data->title;
$video->description_id = $data->description;
$video->source_id = $data->source;
$video->published_at = $data->published_at;
if (!$this->isDuplicate($video)) {
$video->save();
}
}
public function setPublishedAtAttribute($value){
$this->attributes['published_at'] = Carbon::parse($value)->toDateTimeString();
}
public function setTitleIdAttribute($value) {
$this->attributes['title_id'] = (new Title)->store($value);
}
public function setDescriptionIdAttribute($value) {
$description = (new Description)->store($value);
$this->attributes['description_id'] = $description;
}
public function setSourceIdAttribute($value) {
$this->attributes['source_id'] =(new Source)->store($value);
}
public function setAuthorIdAttribute($value) {
$this->attributes['author_id'] = (new Author)->store($value);
}
public function getAuthorIdAttribute($value) {
$this->attributes['author_id'] = (new Author)->getValue($value);
}
public function getTitleIdAttribute($value) {
$this->attributes['title_id'] = (new Title)->getValue($value);
}
public function getDescriptionAttribute($value) {
$this->attributes['description_id'] = (new Description)->getValue($value);
}
public function getSourceIdAttribute($value) {
$this->attributes['source_id'] = (new Source)->getValue($value);
}
public function author() {
return $this->belongsTo('App\Models\Entities\Video\Author', 'author_id');
}
public function description() {
return $this->belongsTo('App\Models\Entities\Video\Description', 'description_id');
}
public function title() {
return $this->belongsTo('App\Models\Entities\Video\Title', 'title_id');
}
public function source() {
return $this->belongsTo('App\Models\Entities\Video\Source', 'source_id');
}
}
video migration file
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateVideosTable extends Migration {
public function up() {
Schema::create('videos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('source_id');
$table->unsignedBigInteger('title_id');
$table->unsignedBigInteger('description_id');
$table->unsignedBigInteger('author_id');
$table->dateTimeTz('published_at');
$table->timestamps();
$table->foreign('source_id')->references('id')->on('sources');
$table->foreign('title_id')->references('id')->on('titles');
$table->foreign('description_id')->references('id')->on('descriptions');
$table->foreign('author_id')->references('id')->on('authors');
});
}
public function down() {
Schema::dropIfExists('videos');
}
}
The foreign key migration files follow this layout for Author, Description, Source, Title
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTitlesTable extends Migration
{
public $timestamps = false;
public function up()
{
Schema::create('titles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('value');
});
}
public function down()
{
Schema::dropIfExists('titles');
}
}
could probably create a foreign migration class and just set the variables that I need
What you're probably looking for is firstOrCreate
There are two other methods you may use to create models by mass
assigning attributes: firstOrCreate and firstOrNew. The firstOrCreate
method will attempt to locate a database record using the given column
/ value pairs. If the model can not be found in the database, a record
will be inserted with the attributes from the first parameter, along
with those in the optional second parameter.
The firstOrNew method, like firstOrCreate will attempt to locate a
record in the database matching the given attributes. However, if a
model is not found, a new model instance will be returned. Note that
the model returned by firstOrNew has not yet been persisted to the
database. You will need to call save manually to persist it.
I have three tables:
notes: id, business_id, note
businesses: id, title, description
businessimages : id, business_id, image
I get my customers notes with this:
$customer = Auth::guard('customer-api')->user();
$notes = Note::where('customer_id', $customer->id)->with('business:id')-
>orderBy('id', 'desc')->get();
Now I want to get notes.id, businesses.id, businesses.title, businesses.description, businessimages.image for each notes and show all of them in one json array
How could I do?
Note::where('customer_id',$customer->id)
->join('businesses', 'businesses.id', '=', 'notes.buisness_id')
->join('businessimages', 'businesses.id', '=', 'businessimages.buisness_id')
->select(notes.id, businesses.id, businesses.title, businesses.description,businessimages.image)
->get();
Note model;
public function business() {
return $this->hasOne('App\Business', 'business_id', 'id');
}
Business mode;
public function businessImage()
{
return $this->hasOne('App\BusinessImage', 'business_id', 'id');
}
Your controller;
$notes = Note::where('customer_id', $customer->id)->with('business.businessImage')->orderBy('id', 'desc')->get();
You should consider using API Resources
This is a great way to organize a model(or a collection of models as well).
App\Note
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
public function business()
{
return $this->belongsTo('App\Business');
}
}
App\Business
namespace App;
use Illuminate\Database\Eloquent\Model;
class Business extends Model
{
public function note()
{
return $this->hasOne('App\Note');
}
public function businessImage()
{
return $this->hasOne('App\BusinessImage');
}
}
App\BusinessImage
namespace App;
use Illuminate\Database\Eloquent\Model;
class BusinessImage extends Model
{
protected $table = 'businessimages';
public function business()
{
return $this->belongsTo('App\Business');
}
}
App\Http\Resources\Note
namespace App\Http\Resources;
class Note
{
public function toArray($request)
{
return [
'noteId' => $this->resource->id,
'businessId' => $this->resource->business->id,
'businessTitle' => $this->resource->business->title,
'businessDescription' => $this->resource->business->description,
'businessImage' => $this->resource->business->businessImage->image
];
}
}
Somewhere in a controller
use App\Http\Resources\Note as NoteResource;
public function foo()
{
$customer = Auth::guard('customer-api')->user();
$notes = Note::where('customer_id', $customer->id)->with(['business','business.businessImage'])->orderBy('id', 'desc')->get();
return NoteResource::collection($notes);
}
I'm working on a jobber search project online using Laravel 5.5.
In my project I want to make a search to find jobbers who live in a certain area and who perform a certain service, or where only one criteria matches.
I use three models: User, Area and Service.
Here is my search bar: I want to use this search bar to do it
This is the User model:
class User extends Authenticatable
{
use Notifiable, EntrustUserTrait;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'surname', 'email', 'phone',
'password','type',];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [ 'password', 'remember_token',];
public function area(): BelongsTo
{
return $this->belongsTo(Area::class);
}
public function service(): BelongsTo
{
return $this->belongsTo(Service::class);
}
}
This is the Service model:
class Service extends Model
{
protected $fillable = ['category_id','name','description'];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function users(): BelongsTo
{
return $this->belongsToMany(User::class, 'service_id');
}
public function jobs()
{
return $this->hasMany('App\Job');
}
}
And this is the Area model:
class Area extends Model
{
protected $fillable = ['town_id', 'name', 'description'];
public function town(): BelongsTo
{
return $this->belongsTo(Town::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'area_id');
}
}
Here is the controller code that did not work for me so far:
public function search(Request $request) {
$service = $request->get('service');
$area = Input::get('area');
if (!(empty($service)) && !(empty($area))) {
$results = User::with(['area', 'service'])
->where('area_id', 'like', "%$area%")
->whereHas('service', function ($query) use ($service) {
$query->where('category_id', $service);
})
->paginate(10);
return view('Search.search', compact('results'));
} elseif (!(empty($service)) && empty($area)) {
$results = User::with(['area', 'service'])
->whereHas('service', function ($query) use ($service) {
$query->where('category_id', $service);
})
->paginate(10);
return view('Search.search', compact('results'));
} elseif (empty($service) && !empty($area)) {
$results = User::with(['area', 'service'])
->where('area_id', 'like', "%$area%")
->paginate(10);
return view('Search.search', compact('results'));
}
}
I would advice you to build your query dynamically depending on the available input. This reduces your code and makes sure you will not have to add new code in multiple places should you extend your search in future.
public function search(Request $request)
{
$query = User::with(['area', 'service']);
if ($request->filled('service')) {
$query = $query->whereHas('service', function ($q) use ($request) {
$q->where('category_id', $request->get('service'));
});
}
if ($request->filled('area')) {
$query = $query->where('area_id', $request->get('area'));
}
$results = $query->paginate(10);
return view('Search.search', compact('results'));
}
As long as you don't call get(), paginate() or find() on the $query, it will be a Illuminate\Database\Eloquent\Builder object. This means you can add additional conditions on the query which will all be included in the actual SQL query and are not performed in-memory (which you clearly don't want).
The method $request->filled('service') will check both of the following two conditions:
$request->has('service')
!empty($request->get('service'))
If you want to be able to search Areas by name, you might need to change the if($request->filled('area')) { ... } part to the following:
if ($request->filled('area')) {
$query = $query->whereHas('area', function ($q) use ($request) {
$q->where('name', 'like', '%'.$request->get('area').'%');
});
}
How can we filter by multiple values in a column?
Example: ?name=comapny1&name=company2
Fore example I have 5 company's and I search for products belong ti company1 and company2 the result I want to get is: table with products that belongs to company1 and company2
This is my filter:
class ProductFilters extends QueryFilter
{
// maker
public function make($maker)
{
return $this->builder->where('company', $maker);
}
And query filter:
<?php
/**
* Created by PhpStorm.
* User: dmitry
* Date: 2017/05/10
* Time: 2:26
*/
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
abstract class QueryFilter
{
protected $request;
protected $builder;
public function __construct(Request $request)
{
$this->request = $request;
}
public function apply(Builder $builder)
{
$this->builder = $builder;
foreach ($this->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
public function filters()
{
return $this->request->all();
}
}
My form looks like this:
<form method="GET" action="/products" id="form1">
<input type="checkbox" name="company[]" value="1">1</input>
<input type="checkbox" name="company[]" value="2">2</input>
</form>
This is my controller:
public function index(Request $request, ProductFilters $filters)
{
$products = Product::filter($filters)->paginate(30)->appends($request->all());
return view('product.browse', compact('products' ));
}
Basic example illustrating use of whereIn.
$companies = $request->company ?? [];
$products = Product::company($companies)->paginate(30);
class Product extends Model
{
// ...
public function company(Builder $query, ...$companies)
{
return $query->whereIn('company', $companies);
}
// ...
}
Example using whereIn with OP's code.
class ProductFilters extends QueryFilter
{
// ...
public function make($maker)
{
return $this->builder->whereIn('company', (array) $maker);
}
// ...
}