How to shorten this eloquent command - php

I have used this Eloquent command to get some data from the database table:
$categories = Category::where('category_type',2)->orWhere('category_type',3)->orWhere('category_type',4)->get();
return view('admin.categories.index', compact('categories'));
So I want category_type of 2,3 and 4 but I wanted to shorten this command, so I tried this instead:
$categories = Category::where('category_type',[2,3,4,5])->get();
But it does not work properly and only show data with category_type of 2!
So the question is how can I shorten these orWhere commands? Is there any way for doing that?

Try whereIn()...
<?php
$categories = Category::whereIn('category_type',[2,3,4])->get();
return view('admin.categories.index', compact('categories'));
https://laravel.com/docs/8.x/queries#additional-where-clauses

This might be an overkill, but turn your glare into:https://github.com/rinvex/laravel-categories.
It's quite hard at the beginning, but gives lots of stuff instead.
Remember that in laravel you can go local/global scope inside your model https://laravel.com/docs/8.x/eloquent#query-scopes
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
You can chain queries this way. The sample is from docs. Don't even think about 'repository pattern'. In my opinion it violates the above functionality and makes your code a bunch of mess. Just play around and make your queries inside model.
When you find yourself in hard situation, when you need to create something from few models involved (model aggregation), you might think about services/helpers/WHATEVER. The same role is given to view models in MVVM, as they prepare data for controller.
These are heavy logic classes, which make most of your work behind and they are called by controller/command-line/anything.

Related

What's the advantage of using Laravel Eloquent Relationship queries?

So I'm trying to improve my queries by implementing eloquent relationships.
Let's say I have a threads table and each thread have replies
What I need is a JSON object of the information of threads with array of all the replies.
Currently, I can achieve what I want with this.
public function show($threadId){
$threads = ThreadView::find($threadId);
$threads->replies = ReplyView::where('threadId', $threadId)->get();
return response()->json($threads, 200);
}
With eloquent relationship, I can achieve the same result with something like this.
public function show($threadId) {
$threads= ThreadView::find($threadId);
$threads->replies= ThreadView::find($threadId)->Replies;
return response()->json($threads, 200);
}
Is there an advantage to using relationship? They both look the same to me.
Using Eloquent you can do this even shorter:
public function show($threadId) {
$threads = ThreadView::with('replies')->find($threadId);
return response()->json($threads, 200);
}
The with function will eager load relations without you having to write the query.
That is the advantage of Eloquent, it writes the queries for you. No need to create join queries or extra queries for you.
public function show($threadId) {
$threads= ThreadView::with('replies')->find($threadId);
return response()->json($threads, 200);
}
The main advantage is that using relationships like this, the first example does 2 selects (it grabs 100% of ThreadView then 100% of ReplyView), while the second example only does 1 query.
The second advantage is that Eloquent does a Really Good Job at lazy loading compared to others and it is much better to use if you are not an expert in SQL queries.
Ditching Eloquent models and using the DB Builder directly will be approximately 500x faster, and I find the code cleaner. That may be what you're looking for.
But if you use models, use the relationships!

Eager load model using with but giving it another name - Laravel 5.2

Is it possible to use eager loading using the with method but giving it another name? Something like:
->with('documents as product', 'documents.documents as categories')
I have a documents table that can be product or categories, eager loading is working but not that friendly to retrieve the documents by just the document name instead of what it really is.
This feature is currently not supported in any Laravel version. Your best bet is to duplicate your relations and name them according to your needs. E.g.:
class Post extends Model
public function documents() {
return $this->hasMany(Document::class);
}
public function products() {
return $this->hasMany(Document::class)
->where('type', 'product'); // Scope the query, if necessary
}
public function categories() {
return $this->hasMany(Document::class)
->where('type', 'category'); // Would a Document really be of type 'category', though? Looks like a code smell.
}
}
$postsWithAllDocs = Post::with('documents')->get();
$postsWithProductsOnly = Post::with('products')->get(); // Only eager load Documents of type 'product'
On a side note, you mention that a Document can be a product or category, which logically doesn't seem to make much sense. Part of the issue could probably be resolved by rethinking the structure of your database and relations.
Eager loading tells "load also this relationship data", so next you can access subject->relation without further queries
if you want to rename the relationship maybe you should do it renaming the relationshp in the model, not in the eager loading
you can also bypass this by adding virtual attributes:
function getProductAttribute(){
return $this->document;
}
leaving eager loading on original document
resulting in product attribute that is the same as document:
$subject->product === $subject->document
I asked myself the same question, and since I didn't find a satisfying answer online, here is what I did.
I had:
$o->load('X');
but I wanted the $o object to have attribute Y with the value of X relation. Since I already had the Y relation defined for $o, I couldn't rename X to Y and finish the job. So I did
$o['Y'] = $o->X();
I know this is not the best solution, but it works for my case :)
Note: load and with produce exactly the same number of sql queries - you need to choose the one which is more appropriate for your situation.

Laravel exclude current id from query eloquent results

I am fairly new to laravel and I built a little "similar posts" section. So every post has a tag and I query all the id's from the current tag. And then I find all the posts with thoses id's. Now my problem is that the current post is always included. Is there an easy way to exclude the current id when querying?
I can't seem to find anything in the helper function on the laravel docs site
this is my function:
public function show($id)
{
$project = Project::findOrFail($id);
foreach ($project->tags as $tag){
$theTag = $tag->name;
}
$tag_ids = DB::table('tags')
->where('name', "=", $theTag)
->value('id');
$similarProjects = Tag::find($tag_ids)->projects;
return view('projects.show', ['project' => $project, 'similarProjects' => $similarProjects]);
}
An easy way to solve your issue would be to use the Relationship method directly instead of referring to it by property, which you can add additional filters just like any eloquent transaction.
In other words, you would need to replace this:
Tag::find($tag_ids)->projects
With this:
Tag::find($tag_ids)->projects()->where('id', '!=', $id)->get()
Where $id is the current project's id. The reason behind this is that by using the method projects(), you are referring your model's defined Relationship directly (most probably a BelongsToMany, judging by your code) which can be used as a Query Builder (just as any model instance extending laravel's own Eloquent\Model).
You can find more information about laravel relationships and how the Query Builder works here:
https://laravel.com/docs/5.1/eloquent-relationships
https://laravel.com/docs/5.1/queries
However, the way you are handling it might cause some issues along the way.
From your code i can assume that the relationship between Project and Tag is a many to many relationship, which can cause duplicate results for projects sharing more than 1 tag (just as stated by user Ohgodwhy).
In this type of cases is better to use laravel's whereHas() method, which lets you filter your results based on a condition from your model's relation directly (you can find more info on how it works on the link i provided for eloquent-relationships). You would have to do the following:
// Array containing the current post tags
$tagIds = [...];
// Fetch all Projects that have tags corresponding to the defined array
Project::whereHas('tags', function($query) use ($tagIds) {
$query->whereIn('id', $tagIds);
})->where('id', !=, $postId)->get();
That way you can exclude your current Project while avoiding any duplicates in your result.
I don't think that Tag::find($tag_ids)->projects is a good way to go about this. The reason being is that multiple tags may belong to a project and you will end up getting back tons of project queries that are duplicates, resulting in poor performance.
Instead, you should be finding all projects that are not the existing project. That's easy.
$related_projects = Project::whereNotIn('id', [$project->id])->with('tags')->get();
Also you could improve your code by using Dependency Injection and Route Model Binding to ensure that the Model is provided to you automagically, instead of querying for it yourself.
public function show(Project $project)
Then change your route to something like this (replacing your controller name with whatever your controller is:
Route::get('/projects/{project}', 'ProjectController#show');
Now your $project will always be available within the show function and you only need to include tags (which was performed in the "with" statement above)

Laravel 4 - Eloquent way to attach a where clause to a relationship when building a collection

This may be a dupe but I've been trawling for some time looking for a proper answer to this and haven't found one yet.
So essentially all I want to do is join two tables and attach a where condition to the entire collection based on a field from the joined table.
So lets say I have two tables:
users:
-id
-name
-email
-password
-etc
user_addresses:
-address_line1
-address_line2
-town
-city
-etc
For the sake of argument (realising this may not be the best example) - lets assume a user can have multiple address entries. Now, laravel/eloquent gives us a nice way of wrapping up conditions on a collection in the form of scopes, so we'll use one of them to define the filter.
So, if I want to get all the users with an address in smallville, I may create a scope and relationships as follows:
Users.php (model)
class users extends Eloquent{
public function addresses(){
return $this->belongsToMany('Address');
}
public function scopeSmallvilleResidents($query){
return $query->join('user_addresses', function($join) {
$join->on('user.id', '=', 'user_addresses.user_id');
})->where('user_addresses.town', '=', 'Smallville');
}
}
This works but its a bit ugly and it messes up my eloquent objects, since I no longer have a nice dynamic attribute containing users addresses, everything is just crammed into the user object.
I have tried various other things to get this to work, for example using a closure on the relationship looked promising:
//this just filters at the point of attaching the relationship so will display all users but only pull in the address where it matches
User::with(array('Addresses' => function($query){
$query->where('town', '=', 'Smallville');
}));
//This doesnt work at all
User::with('Addresses')->where('user_addresses.town', '=', 'Smallville');
So is there an 'Eloquent' way of applying where clauses to relationships in a way that filters the main collection and keeps my eloquent objects in tact? Or have I like so many others been spoiled by the elegant syntax of Eloquent to the point where I'm asking too much?
Note: I am aware that you can usually get round this by defining relationships in the other direction (e.g. accessing the address table first) but this is not always ideal and not what i am asking.
Thanks in advance for any help.
At this point, there is no means by which you can filter primary model based on a constraint in the related models.
That means, you can't get only Users who have user_address.town = 'Smallwille' in one swipe.
Personally I hope that this will get implemented soon because I can see a lot of people asking for it (including myself here).
The current workaround is messy, but it works:
$products = array();
$categories = Category::where('type', 'fruit')->get();
foreach($categories as $category)
{
$products = array_merge($products, $category->products);
}
return $products;
As stated in the question there is a way to filter the adresses first and then use eager loading to load the related users object. As so:
$addressFilter = Addresses::with('Users')->where('town', $keyword)->first();
$users= $addressFilter->users;
of course bind with belongsTo in the model.
///* And in case anyone reading wants to also use pre-filtered Users data you can pass a closure to the 'with'
$usersFilter = Addresses::with(array('Users' => function($query) use ($keyword){
$query->where('somefield', $keyword);
}))->where('town', $keyword)->first();
$myUsers = $usersFilter->users;

SQL SELECT * but with a twist - CodeIgniter

OK lets say I want to select a number of columns from a database table, but I won't know what those columns are in the method. I could pass them in, but it could be more or less depending on the method calling the database method.
A quick fix would be SELECT *, but I understand that this is bad and can cause more data to be returned than is necessary, and I definitely don't need all the data from that table.
So I am using CodeIgniter and prepared statements to do this, and below is what I have currently (it works, just point that out).
function get_pages() {
$this->db->select('pages.id, pages.title, pages.on_nav, pages.date_added, admin.first_name, admin.last_name')
->from('pages, admin')
->where('pages.admin_id = admin.id')
->order_by('pages.id', 'ASC');
$query = $this->db->get();
return $query->result();
}
It's a simple function, but at the moment limited to getting only 'pages'. I want to convert this to work with getting from other tables too. What is the best way?
Many thanks in advance.
EDIT In CodeIgniter I have many Controllers. One for 'pages', one for 'products', one for 'news' and on and on. I don't want to create a single database query method in my model for each controller.
i think the desire to not have 4 methods is misguided. if you don't have the information in the method, you'll have to pass it in. so you could either pass in a string with the table you want and switch over that changing the query based on the table name, or pass in all of the necessary parts of the query. this would include table name, criteria column, criteria, and columns to select. and you'd need to pass that information in every time you called the function. neither of those two methods are really going to save you much code, and they're both less readable than a function for each purpose.
The entire idea with models to put your specific queries to the persistence layer in there. Using a generic catch-all method can be disastrous and hard to test. You should shape your model around the problem you're trying to solve.
This makes it much cleaner and easier to work with. At the same time you must also avoid the common trap of over-sizing models. Each model should follow the SRP. Try and separate concerns so that in your controller, you can easily see state changes.
Does that make sense or am I just rambling...?
In your model:
function get_pages($table_source) {
$this->db->select($table_source.".id"); // or $this->db->select('id');
// for instance, if one of your $table_source ="users" and there is no 'title' column you can write
if($table_source!='users') $this->db->select('title');
$this->db->select('on_nav');
$this->db->select('date_added');
$this->db->select('admin.first_name');
$this->db->select('admin.last_name');
$this->db->join('admin','admin.id = '.$table_source.'.admin_id')
$this->db->order_by('pages.id', 'ASC');
$query = $this->db->get($table_source);
return $query->result_array();
}
In your controller:
function all_tables_info() {
$tables = array("pages","users","customers");
$i=0;
foreach($tables as $table) {
$data[$i++]=$this->your_Model->get_pages($table);
}
//do somthing with $data
}

Categories