Laravel: Accessor to get 'slug' from a polymorphic relationship - php

I am wanting to list all comments. Comments have a Polymorphic relationship with Blog, News etc. When listing all the comments I want to convert the commentable_id to the slug I have saved in the Blog/News table.
So far I have:
public function getCommentableIdAttribute($value)
{
// $post_type = $this->commentable_type;
$post_detail = Blog::find($value);
return $post_detail['slug'];
}
I want to use $post_type and find the value based on that. Naturally though, I am getting a non-object issue if I replace Blog::find() with $post_type->find().
I don't know at this point which table I want to reference.
• Is there a better, more Laravel way of reversing this? The docs seem to suggest that I need to know the Model first.
• If not, any ideas how to make this work?
Thanks in advance for taking the time to help.

Related

SQL Table Relationships with Laravel

I'm fairly new to Laravel and have primarily focused most of my development time on Codeigniter in the past as this is what my job involves for the most part.
I'm currently building a client's website and also building a custom CMS (for both experience and particular needs of the client).
Currently I'm stuggling to figure out how to deal with relationships in Laravel/Eloquent as for example, I have a products table. Each product can have a type. I'm storing these types in a separate table so I've got better control over them in the future when the content starts to build up. All I need is a query to fetch all products with their associated type. In other frameworks, I could simply do this using a query builder to define the columns and joins, however due to the way Eloquent works, I'm struggling to find the way to do this!
Just for a bit of context, in the CMS, there will be a products datatable which will show all products in the system and one of the columns will be type, however I want to show the name of the type, not the id.
Probably me being stupid so feel free to point out something obvious!
What you're looking for is "Eager Loading" which is here in the documentation: https://laravel.com/docs/5.8/eloquent-relationships#eager-loading
In their example on the website, you can replace their "Book" with your "Product" and then their "Author" with your "Type" and you will achieve what you are trying to do.
If you want to load the relationships in the views, i.e. using Blade, you would need to do something like the following:
#foreach($products as $product)
<div> {{ $product->type->name }} </div>
#endforeach
you can search for with() function it will be useful in this :
in your example you need to relate product model with type model so in product you will add
`public function type()
{
return $this->hasOne(Type::class);
}`
and in type add
public function Product()
{
return $this->belongsTo(Product::class);
}
and your query will be
$result=Product::with("type")->get();
this will return the related type row with each related product row as
this example get user with profile
and you can get type name for each product row from
foreach($result as $res){
$type_name=$res->type->name;
}

Eloquent ORM - access to eager-loaded columns

I think I'm running myself in circles here, so I'm hoping to get some help - I should say that I have very little experience with PHP/laravel/eloquent, this is an inherited project.
I have a many-to-many relationship between two classes [Ticket and Comment]. I then have a many-to-one relationship between [Comment and User]. Authors on a Ticket are defined as the distinct set of users who have comments on the ticket.
I would like to eager load author IDs when retrieving tickets (I don't need the whole user, just the id)
Originally authors was given by $this->hasMany(Comments::class)->select('author_id)->distinct()
Which is fine when only retrieving them for a single ticket but will be slow when doing it for all tickets. Attempting to eager load this, even with a foreign key will fail because the ORM can't convert it to a valid SQL statement (I don't think?)
So I though I would define authors based on the comments for a ticket and eager load the comments, then theoretically I can get the distinct authors without having to go back to the database every time:
function comments()
{
return $this->hasMany(Comment::class);
}
function authors()
{
return ($this->comments == null
? $this->hasMany(Comment::class)->select('author_id')->distinct()
: $this->comments->select('author_id')->distinct());
}
I figured then that if I call:
$tickets = Tickets::with('comments')->get();
Then the authors can be determined in memory, but I think lacking a fundamental understanding of what is going on behind the scenes is causing me issues here, as this doesn't work. I suspect I'm approaching this in completely the wrong manner and would appreciate if anyone could point me in the right direction!

Laravel n+ issue eager loading count()

I'm going through my laravel application and trying to fix any n+ issues I can find. I have come across one scenario which isn't really an n+ but not sure what to call it.
I have 2 models Post, Comment. A post has many comments and a comment belongs to a post
When I loop through all my posts I would like to display a count of how many comments they contain. I've been able to do this fine. But the problem it is 2 queries.
How do I update the following Eloquent query to add a column for comments count.
Post::where('status', 1)->get();
Thanks
Update
As of Laravel 5.2.32, a new method was added to the query builder to help with this. When you add the withCount($relation) method to your query, it will add a {relation}_count field to the results, which contains the count of the supplied relation.
So, your query would be:
$posts = Post::where('status', 1)->withCount('comments')->get();
foreach($posts as $post) {
echo $post->comments_count;
}
You can read more in the documentation here.
Original
#JarekTkaczyk has a good blog post that does what you're looking for. Check out the article here.
Basically, you'll be creating a relationship that contains the count of comments for the post, and you'll eager load the relationship (thus avoiding the n+1). He also has some syntactic sugar in there for accessing the count through an attribute accessor.
Either just use count on the relationship, or if you think it's necessary, you could add a 'num_comments' to the Post model and increment it on the creation of a comment:
$post->comments()->count();
or in the comments model:
public function create( $commentData ){
$result = $this->fill( $commentData );
$this->post()->increment('num_comments');
return $result;
}

Custom sorting/filtering in Doctrine 2 associations

I often sort associated entities by a foreign attribute and I'm wondering how to best handle it and still be able to use collections of the main entity. Let's have this example:
Author { $name, Comment[] $comments}
Comment { $name, Category $category}
Category { $name, $position }
I want $author->getComments() sorted by the comment's category position. In terms of DQL:
SELECT com.* FROM comment com JOIN com.category cat ORDER BY cat.position
Truth be told, my sorting criteria are rather more complex, this is just to get us started.
I know about #OrderBy annotation for *ToMany associations but it won't help me because ordering by a joined table's attribute is not supported.
I also know I can use DQL to fetch the comments but I need to access them in many places and I'd prefer referencing $author->getComments() rather than calling $commentRepository->findByAuthorSorted($author). I don't want having to remember to call my custom method to fetch the comments in the right way. I want it to be automatic.
I was considering somehow passing commentRepository to Author entity and use it in getComments() but I didn't figure out how to do it, plus it doesn't feel right to begin with.
I also thought about doing the sorting in PHP in getComments(). I don't think I can use Criteria + $author->comments->matching() because joined attributes don't seem to be supported. I'm fine with casting the collection to a read-only array but I hope there's a better, more Doctrine-way solution.
I don't want to work around the problem by adding $categoryPosition to each Comment.
I'd like to hear how you people handle this problem. I bet I'm not alone :-)

Yii many to many to many filter

I'm relatively new to SQL and even newer to the joys of Yii CDbCriteria. Here's what I'm trying to do:
I have four different classes
Products
Category
ProductHasCategory
ProductLocalization
Products and Category are linked by a many to many relationship (represented by the ProductHasCategory object).
A product can have multiple ProductLocalization.
Here's the scenario: a visitor lands on the view action of a CategoryController. The category has a locale identifier (for example 'en').
I'd like to be able to retrieve the Products available for that category (easy so far), but only the one with a 'en' localization.
I've been able to accomplish that using extremely inefficient ways (eg. for loops*). What would be the best way to accomplish this in the real world?
*Edit: By request, here's the (naive) solution I found so far:
$productWithAppropriateLocalization = array();
foreach ($category->products as $product){
$locale_product = ProductLocalization::model()->find("product_id = :product_id AND locale = :locale", array(":product_id"=>$product->id, ":locale"=>$category->locale));
$productWithAppropriateLocalization[] = $locale_product;
}
$localizedProductsDataProvider = CArrayDataProvider($productWithAppropriateLocalization);
Obviously, this is neither elegant nor optimal; unless I heavily cache the result.
Mmm something like this?
$params['someLocale'] = 'en';
$params['someCategoryId'] = 11;
$params['someId'] = 3; //This is obviously an example, set $params to whatever you need
$condition = 't.Product_id=:someId AND t.locale=:someLocale AND Product.ProductHasCategory.Category.id =:someCategoryId';
$productLocalization = ProductLocalization::model()->with('Product','Product.ProductHasCategory','Product.ProductHasCategory.Category')->find(array('condition'=>$condition, 'params'=>$params));
I'm not 100% sure about the way I'm addressing the category relationship in the condition, but if anything let me know
You can then get the related model with...
$product = $productLocalization->getRelated('Product')->getAttributes();
Please note I don't know the names of your relations, I'd need to look at your model files in order to get that right, but I think it should be something similar to that.
Sounds like what you are after is a parameterized scope on the Localization model.
I would start by making sure you can pull just localizations for 'en' from the product model and work your way back from there. You'll probably need:
a parameterized scope in your product localization model
a parameterized scope in your product model (that scopes your product -> localization model)
use of the above scope with a parameter from your category model
Details are at the bottom of this section: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-named-scopes

Categories