Cant figure out joins - laravel

So I am making a Businesses web app with the filters feature. There are two filters that I have problem with: Order By and Attributes(Has following attributes) features. Which looks like this:
Order By
Highest Rated (radio button)
Most reviews (radio button)
Attributes
Accepts Credit Cards (checkbox)
Accepts Events (checkbox)
Alcohol (checkbox)
Delivery (checkbox)
Smoking (checkbox)
So when Order By option is clicked this function is executed. Where $term is value of order_by get request parameter.
BusinessFilter.php
public function orderby($term)
{
if ($term == 'reviews_count') {
return $this->builder
->leftJoin('reviews', 'businesses.id', '=', 'reviews.business_id')
->groupBy('businesses.id')
->selectRaw('businesses.*, COUNT(reviews.id) as reviews_count')
->orderByDesc('reviews_count');
} else if ($term == 'rating') {
return $this->builder
->leftJoin('reviews', 'businesses.id', '=', 'reviews.business_id')
->groupBy('businesses.id')
->selectRaw('businesses.*, AVG(reviews.rating) AS average')
->orderByDesc('average');
} else {
return $this->builder;
}
}
It works ok and the result is correct.
Now when Attribute have some check boxes this function is executed where $term is an array with set of ids.
BusinessFilter.php
public function attributes($term)
{
$attributes= json_decode($term);
if (count($attributes) == 0) {
return $this->builder;
}
return $this->builder
->select('businesses.*')
->join('business_attribute_value', 'businesses.id', '=', 'business_attribute_value.business_id')
->join('values', 'business_attribute_value.attribute_value_id', '=', 'values.id')
->whereIn('values.id', $attributes)
->groupBy('businesses.id')
->havingRaw('COUNT(*) = ?', [count($attributes)]);
}
the result is correct here too.
Now the problem is when both filters have values it executes both queries together and It doesn't return the correct result. I assume it has something to do with joins. Am I doing something wrong? Please help. And if you need more info or code please let me know. Thank you, you are the best guys!
This is how I execute filters
public function getSearch(BusinessFilter $filters)
{
$businesses = Business::filter($filters)->paginate(30);
return $businesses;
}
This is QueryFilter class. Basically what it does is that it goes through each request parameter and executes its function that was mentioned above.
class QueryFilters{
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 ) ){
continue;
}
if(strlen($value)){
$this->$name($value);
} else {
$this->$name();
}
}
return $this->builder;
}
public function filters()
{
return $this->request->all();
}
}

Related

table relationship and how to use it in laravel controller

so, I have 2 tables, stage and event. Stage hasMany event, and Event belongsTo Stage. And I want to show all stage and its event as json. Here is my code in controller:
public function getschedule(){
$schedule = Stage::all();
//$event = Event_schedule2020::all();
if (!$schedule) {
return response()->json(['msg'=>'Error not found','code'=>'404']);
}
foreach($schedule->events as $array){
$datax[] = [
'id'=>$array->id,
'time'=>$array->time,
'category'=>$array->category,
'type'=>$array->title,
'designer'=>$array->designer,
];
}
foreach ($schedule as $item) {
$jadwal[] = [
'id'=>$item->id,
'date'=>$item->date,
'place'=>$item->stage,
'data'=>$datax,
];
}
return response()->json($jadwal);
}
but I always get this error
the error
so, is there anything I can do about this?
You can utilize inbuilt functions to do what you want to. Laravel automatically transforms model into JSON, no need to built arrays with it.
public function getschedule() {
// tell laravel you want to eager load events
$stages = Stage::with('events')->get();
// laravel knows you loaded events and therefor you can just return it and it does the rest automatically
return response()->json($stages);
}
in your Stage model you have to create relationship like this
public function events()
{
return $this->hasMany('App\Event');
}
then in your Controller
public function getschedule(){
$schedules = Stage::with('events')->get()->toArray();
return response()->json($schedules );
}
your mistake is call events on a collection for solve this you can change foreach like followings :
public function getschedule(){
$schedules = Stage::all(); // I add a 's' to $schedule because is better set plural name;
if (!$schedule) {
return response()->json(['msg'=>'Error not found','code'=>'404']);
}
foreach($schedules as $schedule){
$datax = [];
foreach($schedule->events as $event){
$datax[] = [
'id'=>$event->id,
'time'=>$event->time,
'category'=>$event->category,
'type'=>$event->title,
'designer'=>$event->designer,
];
}
$jadwal[] = [
'id'=>$item->id,
'date'=>$item->date,
'place'=>$item->stage,
'data'=>$datax,
];
}
return response()->json($jadwal);
}
but above solution is not recommended because send many request to server in any foreach loop, following solution is better:
public function getschedule(){
$schedules = Stage::with('events')->get(); // only this difference with above soloution and the rest is the same
if (!$schedule) {
return response()->json(['msg'=>'Error not found','code'=>'404']);
}
foreach($schedules as $schedule){
$datax = [];
foreach($schedule->events as $event){
$datax[] = [
'id'=>$event->id,
'time'=>$event->time,
'category'=>$event->category,
'type'=>$event->title,
'designer'=>$event->designer,
];
}
$jadwal[] = [
'id'=>$item->id,
'date'=>$item->date,
'place'=>$item->stage,
'data'=>$datax,
];
}
return response()->json($jadwal);
}

How to make a filter with query string in laravel

I want to make a filter with query params, here I want to make 3 where, but if one of them is not there, then it will not be a problem because it will display according to the filter only, and if there is no query string then it will display all data
public function VendorInfoFilter(Request $request)
{
$vendor = DB::table('schema.data as d')
->where('d.status','=',$request->status)
->orderBy('d.id')
->get();
return response()->json($vendor);
}
Take as reference, exact code might not work for you.
public function VendorInfoFilter(Request $request)
{
$vendor = DB::table('schema.data as d');
if (!empty($request->status_one)) {
$vendor = $vendor->where('d.status','=', $request->status_one);
}
if (!empty($request->status_two)) {
$vendor = $vendor->where('d.status','=', $request->status_two);
}
if (!empty($request->status_three)) {
$vendor = $vendor->where('d.status','=', $request->status_three);
}
if (empty($request->status_one) && empty($request->status_two) && empty($request->status_three)) {
$vendor= $vendor->where('d.status','=', $request->status_one)->where('d.status','=', $request->status_two)->where('d.status','=', $request->status_three);
}
$result = $vendor->orderBy('d.id')
->get();
return response()->json($result);
}
public function VendorInfoFilter(Request $request)
{
$vendor = DB::table('schema.data as d')
->when($request->status, function ($q, $status) {
return $q->where('d.status','=', $status);
})
->when($request->status_two, function ($q, $status_two) {
return $q->where('d.status_two','=', $status_two);
})
->orderBy('d.id')
->get();
return response()->json($vendor);
}

Eloquent: querying using a non-existing column

I'm trying the following: I have two models (Pub and Schedule) related by a 1xN relationship as follows:
Pub:
/**
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function pubSchedules()
{
return $this->hasMany(Schedule::class);
}
Schedule:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function pub()
{
return $this->belongsTo(Pub::class);
}
Table schedules has the following fields:
id | pub_id | week_day | opening_time | closing_time |
I use the following function to know if one pub is currently (or not) open:
/**
* #return bool
*/
public function isPubCurrentlyOpen()
{
$schedules = Schedule::where([
['pub_id', $this->id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
In my PubController I'd like, when the option "Filter by open pubs" is chosen if($request->openPubs == 1), to show only opened pubs isOpen ==true.
Knowing the relationships between models, how can I do it?
I'm looking for something like this:
if($request->openPubs == 1)
{
$pubs = $pubs->with('pubSchedules')->where('isOpen' == true);
}
Can you help me?
Thanks a lot!
You can do this using a "whereHas"
$openPubs = Pub::whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
})->get();
This is assuming your opening time and closing time are the appropriate time format and not strings (though strings will work as well in a 24h format).
You might achieve something similar to what you are looking for by using a scope e.g.
public function scopeFilterBy($query, $filter = null) {
if ($filter == "isOpen") {
$query->whereHas('schedule', function ($query) {
$query->where('week_day', Carbon::now()->dayOfWeek);
$query->whereRaw(
"'".Carbon::now('Europe/Madrid')->format("H:i:s")."' BETWEEN opening_time AND closing_time"
);
});
}
return $query; //Not sure if this is needed
}
You could then do:
Pub::filterBy($request->openPubs ? "isOpen" : null)->get();
I don't fully understand how you are trying to accomplish this but it should be something like this
$pubs = Pub::with(['pubSchedules' => function ($query) {
$query->where('opening_time', '>' ,Carbon::now()) // make sure it's currently open
->where('closing_time', '<' ,Carbon::now()) // make sure that it's not finished already
->where('week_day', '==' ,Carbon::now()->dayOfWeek) // make sure it's today
}])->find($id);
// to get if pub is currently
if($pub->pubSchedules->count()){
//
}
you can put this code in the model (Pub) and make some changes
if you already have the object you can do this (Add it to model)
public function isPubOpen()
{
$this->load(['pubSchedules' =>
// same code in other method
]);
return (bool) $this->pubSchedules->count();
}
For small tables you could call the function isPubCurrentlyOpen for each element.
For this you would need to change your function to recieve the pub_id as a parameter:
public function isPubCurrentlyOpen($pub_id)
{
$schedules = Schedule::where([
['pub_id', $pub_id ],
['week_day', Carbon::now()->dayOfWeek],
])->get();
foreach ($schedules as $schedule){
$isOpen[] =
Carbon::now('Europe/Madrid')->between(
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->opening_time),
Carbon::now('Europe/Madrid')->setTimeFromTimeString($schedule->closing_time)
);
}
if(in_array(true, $isOpen)){
return true;
//return "Pub Opened";
}
return false;
//return "Pub Closed";
}
and to query the data do:
if($request->openPubs == 1)
{
// assuming $pubs is a collection instance
$pubs = $pubs->filter(function($a){
return $this->isPubCurrentlyOpen($a->id);
})
}
There's a feature in Eloquent called Eager Loading. The Eloquent ORM provides a simple syntax to query for all the Schedules that are related with this particular Pub as described below:
$pubIsOpen= $pub->schedules()
->where([
['week_day', Carbon::now()->dayOfWeek],
['opening_time' , '<' , Carbon::now('Europe/Madrid')],
['closing_time' , '>' , Carbon::now('Europe/Madrid')]
])
->count();
if($openPubCount > 0){
//PUB is open
}else{
//PUB is closed
}
If it helps to someone in the future I post my solution, thanks to #apokryfos:
Pub:
/**
* #param $pubs
* #return mixed
*/
public static function isPubCurrentlyOpen($pubs)
{
$pubs->whereHas( 'pubSchedules', function ($pubs) {
$pubs->where( 'week_day', Carbon::now()->dayOfWeek )
->whereRaw(
"'" . Carbon::now( 'Europe/Madrid' )->format( "H:i:s" ) . "' BETWEEN opening_time AND closing_time"
);
} );
return $pubs;
}
PubsController:
/**
* #param GetPubRequest $request
* #return ApiResponse
*/
public function getPubs(GetPubRequest $request)
{
$orderBy = 'id';
$order = 'asc';
$pubs = Pub::withDistance();
............
if($request->openPubs == 1)
{
$pubs = Pub::isPubCurrentlyOpen($pubs);
}
return $this->response(PubProfileResource::collection($pubs->orderBy($orderBy, $order)->paginate()));
}

laravel many to many with chaining where clause

I have a given table :
tools toolparts parts part_details
----- --------- ----- ------------
id* id* id* id*
name tool_id name part_id
part_id total (int)
----- --------- ----- ------------
the relation between Tools and Parts is ManyToMany. and the relation between parts and part_details is one to many.
with Laravel model, how can I get tool with part that has the biggest part_details.total ??
//tool model
public function parts()
{
return $this->belongsToMany('App\Part', 'tool_part');
}
//part model
public function tools()
{
return $this->belongsToMany('App\Tool', 'tool_part')
}
public function details(){
return $this->hasMany('App\Part_detail');
}
//partDetail model
public function part(){
return $this->belongsTo('App\Part');
}
Controller
public function index()
{
$tools = Tool::with('parts', 'parts.details')->has('parts')->get();
return $tools;
}
what I expected is something like :
Controller
public function index()
{
$tool = Tool::with('SinglePartThatHasHigestTotalInPartDetail');
}
Any Idea ??
You can use Laravel aggregates for querying to get the desired result,
In your code use max() function,
public function index()
{
$tool = Tool::with(['parts.part_details' => function ($query) {
$max = $query->max('total');
$query->where('total',$max);
})->first();
}
I haven't tested this but you can do like this.
Comment if you will get any errors.
I Manage my problem with "hacky" ways. if someone have a better and more elegant solution, please tell me.
//tool model
public function partWithHighestTotalDelivery($trans_date = null){
if (is_null($trans_date)) {
$trans_date = date('Y-m-d');
}
$parts = $this->parts;
$highest_total_delivery = 0;
foreach ($parts as $key => $part) {
$part->detail;
$total_delivery = $part->first_value;
if (isset( $part->detail->total_delivery )) {
$total_delivery += $part->detail->total_delivery;
}
if ($highest_total_delivery < $total_delivery ) {
$highest_total_delivery = $total_delivery;
$part->total_delivery = $highest_total_delivery;
$result = $part;
}
}
if (!isset($result)) {
$result = null;
}
$this->part = $result;
}
In controller i have :
public function index(Request $request){
$tools = Tool::has('parts')
->get();
$tools->each(function($tool){
$tool->partWithHighestTotalDelivery();
});
return $tools;
}
with this, I need to run tool->partWithHighestTotalDelivery() tools.count times. which is take noticeable process if the tools is many.
and also, the code I post and the question I ask has a slightly difference.that's for a simplicity sake's
Use the the hasManyThrough Relationship to get the all part details related to tool and then you can check the one by one record and get the highest total of the tool part.
// Tool Model
public function partsdetails()
{
return $this->hasManyThrough('App\PartDetail', 'App\Part','tool_id','part_id');
}
In Your controller
$data = Tool::all();
$array = [];
if(isset($data) && !empty($data)) {
foreach ($data as $key => $value) {
$array[$value->id] = Tool::find($value->id)->partsdetails()->max('total');
}
}
if(is_array($array) && !empty($array)) {
$maxs = array_keys($array, max($array));
print_r($maxs);// This array return the max total of the tool related parts
}
else{
echo "No Data Available";
}
You can start with the part detail and get the tool(s) from there:
$maxPartDetail = Part_detail::orderByDesc('total')->first();
$tools = $maxPartDetail->part->tools;

Better way for pagination

If I want to do the pagination, I have to fetch data twice, one for get total rows, one for get the rows with limit, for example
<?php
class Admins extends CI_Model
{
public function dataTotal()
{
$total = $this->db->get('admins')->num_rows();
return $total;
}
public function data()
{
return $this->db->limit(10, $this->start)->get('admins')->result();
}
}
Then assign total to pagination and assign the data to view, it's quite make sense, but if there are a lot of conditions, I need to do it twice, for example:
<?php
class Admins extends CI_Model
{
public function dataTotal()
{
$db = $this->db->from('admins')
->where('id >', 1)
->like('name', 'abc', 'both');
return $db->get()->num_rows();
}
public function data()
{
$data = $this->db->from('admins')
->where('id >', 1)
->like('name', 'abc', 'both')
->limit(10, $this->start);
return $data->get()->result();
}
}
More conditions means more duplicated code, any way to make condition filter as one?
You could make a function and use an SQL query as a parameter, that would be the most recommended option. If that's not an option you could do something like this:
public function data($option = "default")
{
if($option == 'default')
{
$data = $this->db->from('admins')
->where('id >', 1)
->like('name', 'abc', 'both')
}
else if($option == 'other')
{
$data = $this->db->from('admins')
->where('id >', 1)
->like('name', 'abc', 'both')
->limit(10, $this->start);
}
return $data->get()->result();
}
And then calling it:
data();
data("other");
That's the most efficient way I can come up with.

Resources