How to get specific columns in Laravel Eloquent with pagination? - laravel

I use this table schema:
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 255)->default('');
$table->text('html')->nullable();
$table->text('json')->nullable();
$table->timestamps();
$table->softDeletes();
});
This is the model:
class Form extends Model
{
use SoftDeletes;
protected $fillable = [
'name',
'html',
'json'
];
protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
}
And in the controller I want to show a list of all items of model but only the id and name fileds. Now I use this, but it show all not hidden fields:
public function index() {
return Form::->paginate(100);
}
This function is only for the list of forms names. But here is the second one for show a form datas for modify:
public function show(string $id) {
$item = Form::findOrFail($id);
return response()->json($item);
}
Of course this last one function needs to be show all fields (id, name, html and json too).
Is there any best practice to show only fields what I needed in the index() function using with paginate()?

If i am not wrong then hopefully you can do it something like this for getting specific columns along with pagination:
return Form::paginate(100,['id','name',.....]);

If I read your question correctly, what you want to do is create a collection of the Form object where only the id and the name fields are actually retrieved on the index overview.
You can do that pretty easily by creating a new collection instance in your controller:
public function index() {
// use the Eloquent select() function
$forms = Form::select('id', 'name')->paginate(100);
return $forms;
}
I would personally put that collection in a repository pattern to make it more easily cacheable. Here's a nice canonical reference to repository patterns in Laravel.
In your show function on your controller you needn't change a thing, considering the ID is still the same.
For future reference, remember that the paginate method paginates only the collection it is called on, and not everything related to a specific model or anything other than that collection. Thus, if you create a new collection in any way, and call the paginate method on that new collection, only whatever is inside that will be paginated. It's pretty powerful stuff! Here's the documentation reference.

Related

laravel hide result for all relations if column has specific value

Lets assume I have a table posts with the fields id and content and published.
A User can have multiple posts, and a post can belong to multiple pages and there might be a lot more relations to a post.
Lets say we have an admin that wants to moderate the posts, the posts should only be visible if approved. So I add the boolean published where posts that are not published 0 should never be visible (only in specific cases e.g. to moderate the post).
Is it possible to set something in the Post model to restrict the related models from loading non published posts.
I want to avoid that I have to filter in the relation, e.g. if I call $user->posts I do not want to check if the posts are published, the non published results should not be available only if i do a search like. Post::where('published','0'). Basically something like softdeletes but than with a custom field.
An example, where the opposite relations are also defined, to make it easier to understand would be:
class Post extends Model
{
use SoftDeletes;
protected $table = 'posts';
public function collection()
{
return $this->belongsTo('App\Collection');
}
public function style()
{
return $this->belongsTo('App\Style');
}
public function pictures()
{
return $this->hasMany('App\Picture')->orderBy('priority', 'asc');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
You can use global query scope in the Model as below to add your desired filters to each query, like below:
// Post.php
class Post extend Model
{
protected static function booted()
{
static::addGlobalScope('published', function (Builder $builder) {
$builder->where('published', true);
});
}
// ...
}
Whenever you don't want to apply the global query scope, use withoutGlobalScope with the name of the query scope, like below:
Post::withoutGlobalScope('published')->get();

Get relationship with eloquent

I am using laravel 6.16.0 and I have two migrations:
person
Schema::create('persons', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('full_name')->nullable($value = true);
$table->date('date_of_birth')->nullable($value = true);
$table->string('phone')->nullable($value = true);
$table->string('office')->nullable($value = true);
$table->timestamps();
});
senator_attributes
Schema::create('senator_attributes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('persons_id')->default('999999')->nullable($value = true);
$table->string('party')->nullable($value = true);
// ...
$table->timestamps();
});
My models look like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
}
class SenatorAttributes extends Model
{
protected $table = 'senator_attributes';
protected $guarded = ['id'];
public function person()
{
return $this->belongsTo(Person::class, 'persons_id');
}
}
I was wondering, how can I query all Persons and get for each person their attributes?
I appreciate your replies!
For pretty straight forward solutions you can directly do:
First, you need to add the SenatorAttributes model inside the Person model. You can do so like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
public function senator_attributes()
{
return $this->hasMany(SenatorAttributes::class, 'persons_id'); // or you can use hasOne if person model is supposed to only have one single senator attribute.
}
}
Now to load senator attributes for each person.
Solution 1, with Eager(early/desperate) Loading:
$persons = Person::with('senator_attributes')->get();
Solution 2, on-demand loading:
$persons = Person::all();
foreach($persons as $person) {
$senator_attribures = $person->senator_attributes;
// write your logic here...
}
Solution 1 is fine as long as you don't load all of the rows at the runtime. If you are planning to load hundreds/thousands of models, consider chunking the solution and use eager loading with it.
Learn more about Laravel Eloquent Relationships here.
Now, read the following if you are interested in best practices.
I don't want to sound nosey, but I personally follow the following ground rules while working on any project.
Table name should always be in plural and model name to be in singular form. For example, persons table will have Person model.
Foreign keys should always be in singular form of referenced table + referenced column of the referenced table. For example, if you wish to add a person reference to senator_attributes table, you should add person_id column to senator_attributes table.
Fact, Laravel supports this naming convention, above all, you save yourself time and maintain the consistency and practice across the teams and projects.

Dynamically use of model to send relationship value in a view in laravel

I am working on a bookmarking type of websites and want to show links related to their respective category on the category page. One link can only have one category.
I have created the model and view but having problem with the code of the controller to get data dynamically.
Links table
{
Schema::create('links', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->text('link');
$table->text('title');
$table->text('description');
$table->integer('category_id');
$table->integer('votes');
$table->dateTime('submitted_at');
$table->rememberToken();
});
Categories table
{
Schema::create('categories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('slug');
$table->string('status');
$table->timestamps();
});
}
Category Model
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = [
'name', 'slug', 'status',
];
public function links()
{
return $this->hasMany('App\Links');
}
}
Links Model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Links extends Model
{
//
protected $fillable = [
'user_id', 'link', 'title', 'description', 'submitted_at', 'category_id','votes',
];
public $timestamps = false;
public function category()
{
return $this->belongsTo('App\Categories');
}
}
Categories controller
public function index($slug)
{
$getcategoryid = DB::table('categories')->where('slug', '=', $slug)->get();
$catid = $getcategoryid[0]->id;
$catlinks=new \App\Category;
$catlinks=$catlinks::find(1)->Links;
return $catlinks;
The problem is I want find(1) to be dynamic according to the page. like if I can use $catid like find($catid) or something?
How can I get data to a category page if is has links then show and if not show a message no link found. like using count()
Seems like you are trying to load the links of a single category given a slug, you can do that like this:
// Do not forget to add this line after the namespace declaration
use App\Category;
public function index($slug)
{
return Category::whereSlug($slug)->firstOrFail()->links;
}
But a nicer way would be to set up route model binding for your category model.
To do so, in your Category class put this method:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
Important: in your route file, make sure to replace the {slug} parameter of your route with {category} otherwise route model binding would not work at all.
Then in your controller class the index function would become:
public function index(Category $category)
{
return $category->links;
}
You can read more about route model binding in the documentation
Updated answer
If you want to customize the response when the category doesn't exists you can do like the code below (in this case without using route model binding).
// Do not forget to add this line after the namespace declaration
use App\Category;
public function index($slug)
{
$category = Category::whereSlug($slug)->first();
if (! $category) {
return response()->json([]);
}
return response()->json($category->links);
}
To get links that belong to a category with $slug you can do:
$links = Link::whereHas('category', function($query) use ($slug) {
$query->where('slug', $slug);
})->get();
You can do it with a JOIN too, but this is simpler and clearer.
Also, when you have models, you don't need to use DB::table(...), and you don't have to make a model instance manually. The instance will be created behind the scenes when you do Link::whereHas.
First of all, your code is a bit all over the place, so for example in Laravel its recommended to keep all Class names singular, but you have used Links instead of Link. 2nd your database structure could be better, for example when you use relationships the column type should match the id of the linking table, so if the id is a bigIncrements, the linking category_id field should be an unsignedBigInteger, also utilise foreign keys.
To try to help you
public function index($slug)
{
$category = \App\Category::where('slug', '=', $slug)->first();
$catlinks = $category->links;
return view('name.of.view', ['links' => $catlinks]);
}
If you learn Route Model Binding you can also do it like so
public function index(\App\Category $category)
{
$catlinks = $category->links;
return view('name.of.view', ['links' => $catlinks]);
}
Then in your view you can use the #forelse blade directive like so
#forelse($links as $link)
Do something with the link
#empty
Message to display if empty
#endforelse
But what I would really recommend is learn Laravel, the docs are extremely good. THen you can do this in your controller:-
public function index(\App\Category $category)
{
return view('name.of.view', ['category' => $category]);
}
Then in your view you can use the #forelse blade directive like so
#forelse($category->links as $link)
Do something with the link
#empty
Message to display if empty
#endforelse
Unfortunately, there is no way of getting a better knowledge and understanding of the framework than to put the time into read and use the docs
remember:
instead of \App\Category or \App\Links you can include it top of controller like this use App\Category; and use App\Links;
instead of
$getcategoryid = DB::table('categories')->where('slug', '=', $slug)->get();
$catid = $getcategoryid[0]->id;
use
$catid = Category::where('slug', '=', $slug)->first();
and instead of :
$catlinks=new \App\Category;
$catlinks=$catlinks::find(1)->Links;
return $catlinks;
use
return catlinks = $catid->links;
this code return all links belong to category
if you want to pass this data to view
use
return view('exampleview',compact('catid'));
and in view you can check catid has links or not
#if($catid->links->count() > 0)
has link
#else
no link
#endif

Laravel Nova: Dynamically bind fields to JSON attribute on model

Laravel Nova
For a particular project, I would like to use the power of the Nova Resource UI combined with a more flexable data model. Specifically, I want to be able to add fields to the resource for attributes stored inside a JSON database field, and not on the table.
Specifics:
Database model: quotations (migration included)
public function up()
{
Schema::create('quotations', function(Blueprint $table)
{
$table->bigInteger('id', true);
$table->timestamps();
$table->string('client_name', 100)->nullable();
$table->string('client_surname', 100)->nullable();
$table->string('status', 10)->nullable()->default('NEW');
$table->text('data')->nullable();
});
}
Nova Resource
So I can define a "normal" NOVA resource and define the following fields (*ignoring status) in App\Nova\Quotation:
public function fields(Request $request)
{
return [
Text::make('Client Name')->sortable(),
Text::make('Client Surname')->sortable(),
];
}
Now my "wish" is to have something to this effect, using the non-existant "bindTo" method to illustrate what I want to achieve
public function fields(Request $request)
{
return [
Text::make('Client Name')->sortable(),
Text::make('Client Surname')->sortable(),
//Fields bound into the JSON data property
Text::make('Client Id')->bindTo('data.client_id),
Date::make('Client Date Of Birth')->bindTo('data.client_date_of_birth),
//etc
];
}
So when a Quotation model is saved, the client_name and client_surname attributes will save to the database as per normal. but client_id and client_date_of_birth should save to the JSON data attribute.
I know I can set up a Mutator on the Quotation model
public function setClientIdAttribute($value)
{
set_data($this->data,'client_id',$value);
}
However that would still require a "non-dynamic" Quoation model. I want to be able to add fields to the view dynmiacally without having to change the Quotation model beyond the basics. A real world example would be where different products have different input fields to gather before generating a quote. I could then easily inject the field deffinition dynamically in the Nova Resource whilst keeping the database model simple.
I also tried the answer proposed to a simpliar question:
Laravel Model Dynamic Attribute
However - the sollution proposed does not work since Nova is still looking for the attribues on the table.
I would love to get some input on how to tackle this requirement.
you can do something like this:
// Model
protected $casts = [
'data' => 'array'
];
// Nova Resource
Text::make('Client Id', 'data->client_id')->resolveUsing(function ($value) {
return $value;
}),

Laravel eloquent attach auto generate random ID

I have an eloquent many to many relationship and I want to use attach() to easily create role_permissions data but the problem is I'm using an UUID for my ID and it throws an error Field 'id' doesn't have a default value. Any way of hijacking the attach() method? so I can set my UUID?
My migration
Schema::create('role_permissions', function (Blueprint $table) {
$table->increments('count')->unique();
$table->string('id')->unique();
$table->string('role_id');
$table->string('permission_id');
$table->timestamps();
});
My model
class Role extends Model
{
//
public $incrementing = false;
public function users()
{
return $this->belongsToMany('App\User', 'user_roles', 'role_id', 'user_id');
}
public function permissions()
{
return $this->belongsToMany('App\Permission', 'role_permissions', 'role_id', 'permission_id');
}
}
My attach code
$role->permissions()->attach($permission_ids);
I know the problem here is that my id is not an incrementing number it's an unique string. My question is how do I "Inject" that unique string to the attach() method? Thank you guys.
The error
Field 'id' doesn't have a default value
refers to the fact that your database does not know how to fill the id field when it's not specified.
Either you edit the schema adding a nullable:
Schema::create('role_permissions', function (Blueprint $table) {
$table->increments('count')->unique();
$table->string('id')->unique()->nullable(); // Bad idea
$table->string('role_id');
$table->string('permission_id');
$table->timestamps();
});
or injecting it via attach:
$role->permissions()->attach($permission_ids, ["id" => null]);
More info on Laravel official doc
Update
For the future developers who encounter this problem you can also set anything inside the attach array, for example:
$role->permissions()->attach($permission_ids, ["id" => Uuid::generate()]);
Update 2
There's also a more clean way to handle this to be honest. I will try to explain it.
You can handle the Pivot events inside the event service provider by simply hooking into the bootmethod:
Here's a snippet
/App/Providers/EventServiceProvider.php
public function boot()
{
Pivot::creating(function($pivot) {
if ($pivot->getTable() == 'role_permissions') {
$pivot->id = Uuid::generate();
}
});
}
Be aware I do not know if this is actually possible on your laravel version. Mine (5.4.*) works as intended
Okay managed to fixed it, what I did with the help of #Claudio Ludovico Panneta's tip.
foreach($permission_ids as $permission_id)
{
$role->permissions()->attach($permission_id, ["id" => Uuid::generate()]);
}

Resources