Merge eloquent collection with query in Laravel? - php

Is it possible to merge an eloquent collection with a query builder?
In this example, I've got 3 tables. An images table, a tag table, and an images_tag table. I also have eloquent relationships in both the Images and Tag models.
Tag.php:
class Tag extends Model {
protected $table = 'tags';
public $timestamps = false;
protected $fillable = [
'tagname',
];
public function Images() {
return $this->belongsToMany('CommendMe\Models\Images');
}
}
Images.php:
public function tags() {
return $this->belongsToMany('CommendMe\Models\Tag');
}
What I'd like to do is something like this:
$tag = Tag::where('tagname', $query)->first();
$imagesWithTag = $tag->images;
$imagesWithoutTag = Images::where('title', 'LIKE', "%{$query}%");
$images = $imagesWithTag + $imagesWithoutTag
->whereIn('mature', [0, $mature])
->where('created_at', '>=', Carbon::now()->subHours($withinHours))
->orderBy($orderByString, 'desc')
->Paginate(50);
Essentially just taking the query (which returns an array from the images table), and the collection (which also returns an array from the images table), combine and sort the results.
Is this possible?
EDIT 2:
When trying Paras's new answer, the following error occurs:
FatalThrowableError in SearchController.php line 98: Call to a member
function toArray() on integer

Try this:
$tag = Tag::where('tagname', $query)->first();
$imageIds = $tag->images()->select('images.id')->get()->pluck('id')->toArray(); // assuming your images table name is "images" and primary key name is "id"
$images = Images::where(function($q) use($imageIds, $query) {
$q->whereIn('id', $imageIds)
->orWhere('title', 'LIKE', "%{$query}%");
})->whereIn('mature', [0, $mature])
->where('created_at', '>=', Carbon::now()->subHours($withinHours))
->orderBy($orderByString, 'desc')->paginate(50);

Related

Laravel withCount use function in model

I have model Post:
protected $guarded = [];
public function reviews() {
return $this->hasMany(Review::class);
}
My reviews table have a column type with values: 1(good),2(comment),3(negative).
I need get count reviews for every type. I need globally get these counts. I know that I can do in model something like this:
protected $withCount = ['reviews'];
But this get me all reviews. But I need get count only for every type.
You could use the withCount method and do sub queries inside:
$counts = Post::withCount([
'reviews',
'reviews as good_reviews' => function ($query) {
$query->where('type', 1);
}],
'reviews as bad_reviews' => function ($query) {
$query->where('type', 3);
}],
}])->get();
You can access the count like this:
echo $counts[0]->good_reviews;
For more info: Docs
you can use GroupBy method for it.
Something like this:
DB::table('reviews')
->select('type', DB::raw('count(*) as total'))
->groupBy('type')
->get();

Laravel paginate pivot tables

I have collections which contains custom products I need to paginate those collections products but I receive error.
data
With this query I can get my collection and it's products but I'm not able to paginate products.
$collection = Collection::where('slug', $slug)->where('status',
'active')->with('products')->first();
With this query I receive error
$collection = Product::with('collections')->whereHas('collections',
function($query) use($slug) { $query->where('slug',
$slug)->where('status', 'active')->first(); });
Error
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'shopping.product_id' doesn't exist (SQL: select * from `collection_products` inner join `product_id` on `collection_products`.`id` = `product_id`.`collection_product_id` where `products`.`id` = `product_id`.`product_id` and `slug` = test and `status` = active limit 1)
Code
Product model
public function collections()
{
return $this->belongsToMany(CollectionProduct::class, 'product_id');
}
Collection model
public function collection(){
return $this->belongsToMany(CollectionProduct::class);
}
public function products(){
return $this->belongsToMany(Product::class, 'collection_products', 'collection_id', 'product_id');
}
CollectionProduct model
public function collection()
{
return $this->belongsTo(Collection::class, 'collection_id','id');
}
Controller default query
public function single($slug){
$collection = Collection::where('slug', $slug)->where('status', 'active')->with('products')->first();
return view('front.collections.single', compact('collection'));
}
Question
How can I get my collection products with pagination ability?
Couple things:
You are trying to call the first() method inside of a relationship query in this line:
$collection = Product::with('collections')->whereHas('collections', function($query) use($slug) { $query->where('slug', $slug)->where('status', 'active')->first(); });
The methods first() and get() are used to execute the query, so you should keep them at the end of the chain of eloquent methods:
$collection = Product::with('collections')
->whereHas('collections', function($query) use($slug) {
$query->where('slug', $slug)->where('status', 'active');
})
->get();
https://laravel.com/docs/5.7/eloquent#retrieving-models
However, if you want to paginate the list of products, then what you really want is the paginate() method:
$collection = Product::with('collections')
->whereHas('collections', function($query) use($slug) {
$query->where('slug', $slug)->where('status', 'active');
})
->paginate(20); // will return 20 products per page
https://laravel.com/docs/5.7/pagination
Also, the collections() method on your Product model has product_id listed as the join table, and is joining to the CollectionProduct model instead of the Collection model.
Try this instead for your Product model:
public function collections()
{
return $this->belongsToMany(Collection::class, 'collection_products', 'product_id', 'collection_id');
}
i use following code and working very well
the first step get product
$product = Product()->find($product_id);
than i get collections and pagination
$collections = Product::find($product_id)->collections->paginate(20);
for example i used above code in this site.

Laravel querying data through a pivot table

I have a table called 'projects' and a table called 'tags'. These 2 are connected through a pivot table named 'project_tag' so for example like this:
There is 1 record in the projects table with project_id 1
then in the 'project_tag' table there is this :
project_id 1 and tag_id 1
and tag_id 1 is "sometag" for example
Now my problem is I want to query all the projects that has "sometag" as name in the tags table.
Is there a way to do this? Or should I work with the id's instead of the tag values?
What my models look like:
Project model:
public function tags()
{
return $this->belongsToMany('App\Tag')->withTimestamps();
}
Tag model:
public function projects()
{
return $this->belongsToMany('App\Project');
}
I am a bit lost in my database structure :) I am fairly new to laravel
Many thanks in advance!
Assuming your model is called Project and your project-tag relationship is called tags(), this should work out for you:
Project::whereHas('tags', function ($query) {
$query->where('name', 'like', '%sometag%');
})->get();
To query projects by certain tags you ca do like this:
// Get projects by a single tag
$tag = 'mytag';
Project::whereHas('tags', function ($q) use ($tag) {
$q->whereName($tag);
// or $q->whereSlug($tag);
})->get();
// Get projects by multiple tags
$tags = ['mytag', 'othertag', 'iamtag'];
Project::whereHas('tags', function ($q) use ($tags) {
$q->whereIn('name', $tags);
})->get();
Or you can query projects from pivot table like this:
// Get projects by a single tag
$tag_id = 1;
Project::whereIn('id', function($q) use ($tag_id) {
$q->select('project_id')->from('project_tag')->where('tag_id', $tag_id);
})->get();
// Get projects by multiple tags
$tag_ids = [1, 2, 3];
Project::whereIn('id', function($q) use ($tag_ids) {
$q->select('project_id')->from('project_tag')->whereIn('tag_id', $tag_ids);
})->get();
Adding to what #TheFallen has said your models should look like this
class Tag extends Model
{
/**
* Get all of the tags project
*/
public function projects()
{
return $this->belongsToMany('App\Project');
}
}
class Project extends Model
{
/**
* Get all of the project's tag
*/
public function tags()
{
return $this->belongsToMany('App\Tag');
}
}
Then you can do this
Project::whereHas('tags', function ($query) {
$query->where('name', 'like', '%sometag%');
})->get();

Laravel eloquent get all records wherehas all ids in many to many relation

I have a Posts table it has three fields id, title, description.
My Post Model
class Post extends Model
{
use SoftDeletes;
protected $fillable = ['title', 'description'];
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tag');
}
}
My Tag Model
class Tag extends Model
{
use SoftDeletes;
protected $fillable = ['name'];
public function posts()
{
return $this->belongsToMany(Post::class, 'post_tag');
}
}
Now I want to get posts & paginate where I have a tag filter e.g I have two tags animals & news which has id 1 & 2. Now I want to get all posts which has tag 1 & 2 & paginate. Here is what I tried
Post:: with('tags')->whereHas('tags', function($q) {
$q->whereIn('id', [1, 2]);
})->paginate();
But here as I am whereIn it returns posts has tags 1 or 2 or both. But I want post who has both tag id 1 & 2.
I am using Laravel 5.2.
You'll have to loop through your list of ids to add that condition, then. For instance:
$query = Post::with('tags');
foreach ($ids as $id) {
$query->whereHas('tags', function($q) use ($id) {
$q->where('id', $id);
});
}
$query->paginate();
I have been looking for the same thing and inspired by this stackoverflow MySQL answer, I have ended up with this
Code:
Post:: with('tags')->whereHas('tags', function($q) {
$idList = [1,2];
$q->whereIn('id', $idList)
->havingRaw('COUNT(id) = ?', [count($idList)])
})->paginate();
Because I think I might use it in a few places I have made it into a trait which you can view here. Which if you included the trait in your Post class you could use like the following.
Code:
Post::with('tags')->whereHasRelationIds('tags', [1,2])->paginate();
You can also use whereHas()'s third and fourth parameter in combination with whereIn():
$keys = [1, 2];
$list->whereHas(
'genres',
fn ($query) => $query->whereIn('id', $keys),
'=',
count($keys)
);
I don't think there's a built in method for this, but I would suggest putting the foreach loop inside the whereHas method just for neatness sake.
$query = Post::with('tags')->wherehas('tags', function ($q) use ($ids) {
foreach ($ids as $id) {
$q->where('id', $id);
}
})->paginate(10);

Laravel 5 where clause from another table

I have a simple code and what I want to do is to access a field from another table and put it in my where clause. This is my code:
ReportController.php
$reservations = Reservation::with('charge', 'room', 'client')
-> whereBetween('reservation_from', [$from, $to])
-> where('room.type', \Request::input('type')) //what should this be
-> orderBy('created_at')
-> get();
Room.php
class Room extends Model
{
use SoftDeletes;
protected $table = 'rooms';
protected $fillable = ['id', 'roomNumber', 'type', 'price', 'description'];
public function reservations() {
return $this->hasMany('App\Reservation', 'id', 'room_number');
}
}
Reservation.php
class Reservation extends Model
{
use SoftDeletes;
protected $table = 'reservations';
protected $fillable = ['roomNumber', 'clientId', 'reservation_from', 'reservation_to'];
public function room() {
return $this->belongsTo('App\Room');
}
}
Schema:
As you can see in the ReportController.php, there is a comment saying "what should this be", that's the part that I want to fix. What I wanted to do is access the type field in the rooms table in my eloquent query.
The query that I want to do is like this:
select * from `reservations` where `reservation_from` between '2015-10-29' and '2015-10-29' and `rooms.type` = "test"
Is there a way to do this? Thank you.
What you are looking for is the whereHas method.
$reservations = Reservation::with('charge', 'room', 'client')
->whereBetween('reservation_from', [$from, $to])
->whereHas('room', function($query) {
$query->where('type', '=', \Request::input('type'));
})
->orderBy('created_at')
->get();
Link to docs: http://laravel.com/docs/5.1/eloquent-relationships#querying-relations
Edit:
Editing this to clarify some things in the comments.
To create convenient, reusable query constraints to make your code cleaner, you can use query constraints: http://laravel.com/docs/5.1/eloquent#query-scopes
Also, because queries can be chained, you can do something like this:
// Create query with common constraints
$query = Reservation::with('charge', 'room', 'client')
->whereBetween('reservation_from', [$from, $to]);
// Ternary operator to decide whether or not to add whereHas constraint
$query = (\Request::input('type') == "all") ? $query : $query->whereHas('room', function($query) {
$query->where('type', '=', \Request::input('type'));
});
// Finally, fetch the results sorted by 'latest', which is a convenient way of doing "orderBy('created')"
$reservations = $query->latest()->get();
I believe you are looking to do this. Updated per your question update. The with method takes a string or an array.
$reservations = Reservation::with(['charge', 'client', 'room' =>
function($query){
$query->where('type', \Request::input('type'));
}])
->whereBetween('reservation_from', [$from, $to])
->orderBy('created_at')
->get();

Categories