Laravel Nova Override BelongsTo Language field based on already translated resources - laravel

Imagine the following 3 models with fields:
Listing:
id
ListingTranslation:
id
listing_id
language_id
title
Language:
name
iso
Inside my ListingTranslation create/update form how can I filter the language selector to NOT SHOW the languages that have been already translated?
(i.e. If I have 2 languages ES (id 1) and EN (id 2) and if I have a listing with id 1 and this listing already has a listing_translation with id 1, listing_id 1 and language_id 1, the language selector should only show EN as an option).
Language selector:
BelongsTo::make('Language')
Laravel Nova documentation provides the following method to filter the queries that are used to populate relationship model selection menus:
public static function relatableQuery(NovaRequest $request, $query)
{
return $query->where('user_id', $request->user()->id);
}
However, I do not know how to access something like listing_id from this method.

Instead of creating Nova resource for pivot table ListingTranslation, you can make use of BelongsToMany relation.
Under Listing Nova resource fields add BelongsToMany::make('Languages'). Assuming you have already defined the relationship in Listing model.
At this point when you attach a language which is already attached, you will see an error message The language is already attached
But if you still want to stop listing the already attached languages you can add relatableQuery given below under Listing nova resource.
public static function relatableLanguages(NovaRequest $request, $query)
{
$listing = $request->findResourceOrFail();
return $query->whereNotIn('id', $listing->languages->pluck('id'));
}
Hope this will help you.

Related

Laravel Polymorphic Many-to-Many relationship pivot table with relationship to another Model

I have the following table structure as shown in the diagram:
Briefly, it is composed of several many-to-many polymorphic relationships as described:
many resources can have many sources and the pivot table sourceables contains catalog_number and lot_number information to make each row in the pivot table unique. Many resources could also come from the same source or from different sources, differentiated by the catalog number and lot number on the pivot table.
many resources can also have many publications attached to it, through the publicationables table with notes on the pivot table
a resource's source could also be described in many publications.
My questions:
Since the resource's source is differentiated by the pivot table sourceables how should I save the relationship between the pivot rows of sourceables to the publications?
Can you have a custom intermediate table models between both sourceables and 'publicationables' to link to the publications?
How to retrieve a resource with all it's publications and also with the sources with all corresponding publications?
Here is my answer and I hope that I can bring some light to your problem. I already publish a GitHub repository with an example of all the code I write here. I add more information about how to replicate my scenario there.
The Database and The Relations
Here is my interpretation of the Database and its relations. You can check all the Migrations on the repository.
The Solution
Question 1:
How should I save the relationship between the pivot rows of sourceable to the publications?
Answer:
Before proceeding with the code example, I would like to explain some important concepts to understand. I'm going to use the expression tag to refer to the identifier or index Morph Relations used to relate models.
The way this works, it's by assigning the tag to any Model you want to add into a relation. Any model using these tags can be store in the Morph Pivot Table. Laravel uses the _"modelable"type column to filter the call on the relations storing the Model Name. You can "tag" your Model with a Relation creating a method into the Model that returns the morphToMany relation function.
For this specific case, here's how to proceed:
In your Resource Model, you have two methods, one related to the sourceable index and the other with the publicationable tag using morphToMany in return.
Here's how it's look the Resource Model (./app/Models/Resource.php):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Resource extends Model
{
use HasFactory;
protected $guarded = [];
public function publications()
{
return $this->morphToMany(Publication::class, 'publicationable')->withPivot('notes');
}
public function sources()
{
return $this->morphToMany(Source::class, 'sourceable')->withPivot(['catalog_number', 'lot_number']);
}
}
In your Publication Model, you have two methods, one related to the sourceable index and the other with the inverse relation with the Resource Method to the publicationable tag using morphedByMany in return.
Here's how it looks the Publication Model (./app/Models/Publication.php):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Publication extends Model
{
use HasFactory;
protected $guarded = [];
public function sources()
{
return $this->morphToMany(Source::class, 'sourceable')->withPivot(['catalog_number', 'lot_number']);
}
public function resources()
{
return $this->morphedByMany(Resource::class, 'publicationable');
}
}
With this, you can be able to accomplish your goal of relating Publications with Resources and Sources.
Question 2: Can you have an intermediate table between both sourceable and publicationable to link to the publications?
Answer:
No, you don't need to. You can use the sourceables table to accomplish this. You can always relate a Source with ANY model by creating the method that returns the morphToMany relation to the Source model. These what we do with Publications on Question 1.
Question 3: How to retrieve a resource with all its publications and the sources with all corresponding publications?
Answer:
I think Eloquent it's my favorite feature on the whole Laravel Framework. This the cherry on the cake with all we do on the Model definition.
If you check the Resource and Publication Model definition again, we add a withPivot() method with the related field we want to include on any call we do to the relation with eloquent. This method made it possible to read custom values from the Pivot table.
IMPORTANT: For this example, I'm implicitly adding the pivot values because I don't declare those columns as NULL on the migrations.
To relate (Store on the Pivot table) a publication with a resource using the relation, you just need to:
(Using artisan tinker)
Psy Shell v0.10.8 (PHP 8.0.6 — CLI) by Justin Hileman
>>> $publication = \App\Models\Publication::find(5)
>>> $resource = \App\Models\Resource::find(19)
>>> $resource->publications()->attach($publication, ["notes" => "Eureka!"]);
### Adding another Publication
>>> $publication = \App\Models\Publication::find(10)
>>> $resource->publications()->attach($publication, ["notes" => "Eureka 2!"]);
(Using a Controller)
use App\Models\Resource;
use App\Models\Publication;
...
$id_resource = 1; // This is the Resource Id you want to reach.
$id_publication = 10; // This is the Resource Id you want to reach.
$resource = Resource::find($id_resource);
$publication = Publication::find($id_publication);
$pivotData = [ "notes" => "Eureka!" ];
$resource->publications()->attach($publication, $pivotData);
To retrieve all publications from a resource, you just need to:
(Using artisan tinker)
Psy Shell v0.10.8 (PHP 8.0.6 — CLI) by Justin Hileman
>>> $resource = \App\Models\Publication::find(5)
>>> $resource->publications()->get();
Easy right? :) Eloquent POWER!
(Using a Controller)
use App\Models\Resource;
...
$id_resource = 1; // This is the Resource Id you want to reach.
$resource = Resource::find($id_resource);
$resource->publications()->get();
Just in case of any doubt, this is how you can store and retrieve from all the models:
(Using a Controller)
use App\Models\Publication;
use App\Models\Resource;
use App\Models\Source;
...
... Method ...
$id_publication = 1;
$id_resource = 1;
$id_source = 1;
$publication = Publication::find($id_resource);
$resource = Resource::find($id_resource);
$source = Source::find($id_resource);
$publicationPivotColumns = [
"notes" => "This is a note...",
];
$sourcePivotColumns = [
"catalog_number" => 100,
"lot_number" => 4903,
];
// Storing Data
// Attach (Store in the publicationables table) a Publication to a Resource
$resource->publications()->attach($publication, $publicationPivotColumns);
// Attach (Store in the sourceables table) a Source to a Resource
$resource->sources()->attach($source, $sourcePivotColumns);
// Attach (Store in the sourceables table) a Source to a Publication
$publication->sources()->attach($source, $sourcePivotColumns);
// Retraiving Data
// Get all Sources from a Resource
$resource->sources()->get();
// Get all Publications from a Resource
$resource->publications()->get();
// Get all Sources from a Publication
$publication->sources()->get();

Laravel nova and belongsToThrough?

Is there a way to display in Laravel Nova Resource a relation like this ?
$report->meter->user
where
- a report belongs to a meter
- a meter belongs to a user
I am in report ressource and I want to display related user
I struggled with this lately. What we seek is a BelongsToThrough relationship field. That type of relationship isn't natively supported by Laravel, which means there's no field for it in Nova either. However, you can easily implement it in Laravel by installing this package: https://github.com/staudenmeir/belongs-to-through
You can then add your report relationship to the User model.
// App\Models\Report
use \Znck\Eloquent\Traits\BelongsToThrough;
public function report()
{
return $this->belongsToThrough('App\Models\User','App\Models\Meter');
}
And you can use a regular BelongsTo relationship within Nova.
// App\Nova\Report
public function fields(Request $request)
{
return [
BelongsTo::make('User')
]
}
Note: This would work for the Report index/details views, but you'll want to exclude it from the edit form. (Consider a computed Text field, if you want to display the user on the edit form.)

Laravel Nova access related model id from resource

Having:
Listing:
id
user_id
...
ListingTranslation:
id
listing_id
language_id
...
Language
id
iso_code
...
One to Many Relationship between Listing and ListingtTranslation
Listing hasMany ListingTranslation
ListingTranslation belongsTo Listing
One to Many Relationship between Language and ListingTranslation
Language hasMany ListingTranslation
ListingTranslation belongsTo Language
I am looking for a way to access listing_id from ListingTranslation Resource.
The related Listing field is declared in ListingTranslation Resource:
BelongsTo::make(__('Listing'), 'listing', 'App\Nova\Listing'),
The aim is to be able to have a select field in Listing Translation Resource with the available languages so a Listing can have only One ListingTranslation for available Language. The Language select field will show a validation error if the user selects a Language that has already a created ListingTranslation for that particular Language.
The relationship is eager loaded in my ListingTranslation resource:
public static $with = [
'listing'
];
I can use it in title and subtitle methods. However, I cannot access it from the fields method:
BelongsTo::make(__('Language'), 'language', 'App\Nova\Language')
->rules("unique:listing_translations,language_id,NULL,id,listing_id,{$this->listing->id}"),

Laravel nova overriding/setting custom value for the Fields or Has Through relationship

I am developing a Web application using Laravel. For the admin panel, I am using Laravel Nova. What I am trying to do now is that I need to use data from the table which has relationship through another table. To be, clear, see my database structure below.
items
=====
id
name
price
sub_category_id
sub_categories
==============
id
name
parent_category_id
parent_categories
=================
id
name
What I am trying to achieve inside the Nova is that I want to display the parent category name of the item on the item index/list page. The first thing is that I do not want to create custom attribute something like this in the model
protected $appends = [
'parent_category_name'
];
function getParentCategoryNameAttribute()
{
//code here
}
Therefore, there are two solutions I can think of. The first solution is using the HasThrough relationship. But I cannot find it in Nova. So, I cannot use it. The second solution is that overriding the field value on render. Something like this.
Text::make("fieldname")->fillUsing(function($request, $model, $attribute, $requestAttribute) {
//$model->sub_category->parent_category - then I can return a value
return "Parent category name";
})->onlyOnIndex()
But the above code is not working. So, what would be the best approach to handle the has-through relationship in Nova?
Assuming you have defined the relationship sub_category & parent_category properly.
Define the relationship in Item model as below
public function parent_category()
{
return $this->sub_category->parent_category();
}
Then use it in Item resource as below.
BelongsTo::make('Parent Category')
->hideWhenCreating()
->hideWhenUpdating(),

SEO friendly URLs with category/subcategories/article slug? [Laravel]

First of all, I have Article model and articles table in the database. Each article can be shown using Laravel's standard URI structure: www.example.com/articles/5 (where 5 the article id.). Each article has a slug field (slug column in the articles table) , so with Route Model Binding it is easy to change this and have a slug instead of id in the URI:
In RouteServiceProvider.php I just added:
public function boot(Router $router)
{
parent::boot($router);
\Route::bind('articles', function($slug) {
return \App\Article::where('slug', $slug)->firstOrFail();
});
}
... and now I can open articles with: www.example.com/articles/this-is-some-slug .
On the other hand, each article belongs to one category. For example, let's say that there are the following categories:
Politics
Sport
Football
Tennis
ATP
WTA
Culture
I created these categories by using Baum (an implementation of the Nested Set pattern for Laravel 5's Eloquent ORM). So there is a Category model and categories table in the database:
$table->increments('id');
$table->string('name');
$table->integer('parent_id')->nullable();
$table->integer('lft')->nullable();
$table->integer('rgt')->nullable();
$table->integer('depth')->nullable();
$table->timestamps();
Of course, in articles table there is a column category_id because of One-to-Many relationship (one Article belongs to one Category, one Category can have many Articles).
All articles belonging to some category can be displayed via the following URL: www.example.com/articles/category/1 (where 1 is the id). If we add slug column to the categories table & set Route Model Binding :
\Route::bind('category', function($slug) {
return \App\Category::where('slug', $slug)->firstOrFail();
});
then we will use a slug instead of id: www.example.com/articles/category/politics (this will display all the articles belonging to the category politics).
But I would like to have URIs with the following structure:
www.example.com/sport/tennis/wta/article_slug (/category/subcategory/subcategory/article_slug)
www.example.com/politics/article_slug (/category/article_slug )
and so on...
The problem is that I have no idea how to do this with Laravel. Is it even possible? How would you solve this problem?
Thanks in advance and sorry for my bad English.
SEO friendly URLs with category/subcategories/article slug?
To produce a url that's example.com/category/subcategory/article is pretty simple, but you're obviously looking to add the complexity of multiple subcategories. For that we'll need to look at route parameter regex constraints.
Using regex in your route parameter constraints you can get the subcategories as a string eg. subcategory1/subcategory2 and then pass it via another custom binding or directly to your controller.
Here's an example of the route you need with the route parameter constraint added:
// Route to closure
Route::get('/{category}/{subcategories}/{article}', function($category, $subcategories, $article)
{
return $subcategories;
})->where('subcategories', '(.*)');
// Route to controller method
Route::get('/{category}/{subcategories}/{article}', 'ArticlesController#show')->where('subcategories', '(.*)');
And an example of a custom binding for your subcategories parameter that returns the subcategories as an array:
$router->bind('subcategories', function ($value) {
return explode('/', $value);
});
Caveat: The only problem you will run into using the route parameter bindings in the way you've described, is that the article loaded here is only dependant on the slug being correct. It will still load if the categories in the url are unrelated, which you'll need to take care of in your controller logic.

Resources