Call a Model method using Laravel Relationship - php

I'm currently trying to use Laravel Relationships to access my achievements Model using User model, I use this relationship code:
public function achievements()
{
return $this->hasMany('App\Models\User\Achievement');
}
I can easily make some eloquent queries, however I can't access any method that I created there, I can't access this method:
class Achievement extends Model
{
public function achievementsAvailableToClaim(): int
{
// Not an eloquent query
}
}
Using the following code:
Auth::user()->achievements()->achievementsAvailableToClaim();
I believe that I am using this Laravel function in the wrong way, because of that I tried something else without using relationship:
public function achievements()
{
return new \App\Models\User\Achievement;
}
But that would have a performance problem, because would I be creating a new class instance every time I use the achievements function inside user model?
What would be the right way of what I'm trying to do?

it's not working because your eloquent relationship is a hasMany so it return a collection. you can not call the related model function from a collection.
you can var dump it on tinker to understand more what i mean.

You can use laravel scopes.Like local scopes allow you to define common sets of constraints that you may easily re-use throughout your application.
In your case you use this like, Define scope in model:
public function scopeAchievementsAvailableToClaim()
{
return $query->where('achivement_avilable', true);
}
And you can use this like :
Auth::user()->achievements()->achievementsAvailableToClaim();

Related

Laravel 8.x realationship - Use HasMany in HasOne

I'm trying to use a HasMany relation in a HasOne.
I have following Models:
class Auction extends Model
{
//...
public function bids(): HasMany
{
return $this->hasMany(Bid::class, 'auction_id');
}
public function approvedBids(): HasMany
{
return $this->bids()->approved();
}
public function topBids(): HasMany
{
return $this->approvedBids()->orderByDesc('price')->take(10);
}
public function topBid(): HasOne
{
//return $this->topBids()->firstOfMany(); // Not Working
//return $this->hasOne(Bid:class, 'auction_id)->ofMany('price','max')->approved(); // not working
//return $this->hasOne(Bid:class, 'auction_id)->approved()->ofMany('price','max'); // not working
//return $this->hasOne(Bid::class, 'auction_id')->ofMany('price', 'max'); // working but not as I expecting
}
}
class Bid extends Model
{
//...
public function scopeApproved(Builder $query): Builder
{
return $query->where('state', BidState::STATE_APPROVED);
}
//...
}
As you can see in the source, I'm looking for a way to make a relation that retrieve the Top Bid (ONE BID) from topBids() relation, but I don't know how, and none of my approaches works:
$this->topBids()->firstOfMany(); // Not Working
$this->hasOne(Bid:class, 'auction_id')->ofMany('price','max')->approved(); // not working
$this->hasOne(Bid:class, 'auction_id')->approved()->ofMany('price','max'); // not working
Unfortunately these shouldn't be a relationships
Real question is why are you trying to make these relationships?
Usually you should be using relationships on model to describe how they are correlating together within the database, the rest of the things you should be defining as a scope on a query or a model, or as an attribute.
So, what I'm trying to say is this:
Keep bids as a relationship, as that is actually a relationship to the Bid model
Update approvedBids to be a scope (or an attribute)
Update topBids to be a scope (or an attribute)
Then, you will be able to find top bid easily by doing something like this:
$this->topBids->first() -> if it is an attribute
$this->topBids()->first() -> if it is a scope
This is how you can create a scope: https://laravel.com/docs/9.x/eloquent#local-scopes
In the end, you can even create an attribute that will allow you to retrieve topBid like this:
public function getTopBidAttribute(){
$this->bids()->approved()->orderByDesc('offered_token_price')->first();
}
Then later you can just do $this->topBid.
I think I've found the solution
public function topBid(): HasOne
{
return $this->hasOne(Bid::class, 'auction_id')
->approved()
->orderByDesc('price');
}
You see the problem was in ofMany() function, which creates a huge SQL and I don't know why!
I've returned a HasOne object here, which supports all kinds of query manipulations. Basically HasOne class, tells the main query, to:
Retrieve the first record of the query I've provided.
So if we use orderBy it only provides an order for HasOne's query. and the main query will take cares of the rest and selects the first record.

Laravel 5.8 newing up eloquent model and calling accessor in one line?

I have a contact_info_scopes table and one of the scopes is 'Default', which is likely to be the most common scope called, so I'm creating an accessor
public function getDefaultScopeIdAttribute()
{
return $this::where('contact_info_scope', 'Default')
->first()
->contact_info_scope_uuid;
}
to get the defaultScopeId and wondering how I can new up the ContactInfoScope model and access that in one line. I know I can new it up:
$contactInfoScope = new ContactInfoScope();
and then access it:
$contactInfoScope->defaultScopeId;
but I would like to do this in one line without having to store the class in a variable. Open to any other creative ways of tackling this as well since an accessor may not really be ideal here! I'd be fine with just creating a public function (not as an accessor), but would have the same issue of calling that in one line. Thanks :)
You should be able to call the model and chain the value if you return the instance in its constructor method
(new ContactInfoScope)->defaultScopeID
Not tried it in Laravel but works in plain ol PHP
//LARAVEL
//write on model ----------------------------------
protected $appends = ['image'];
public function getImageAttribute()
{
$this->school_iMage = \DB::table('school_profiles')->where('user_id',$this->id)->first();
$this->studend_iMage = \DB::table('student_admissions')->where('user_id',$this->id)->first();
return $this;
}
//call form anywhere blade and controller just like---------------------------
auth()->user()->image->school_iMage->cover_image;
or
User::find(1)->image->school_iMage->cover_image;
or
Auth::user()->image->school_iMage->cover_image;
// you can test
dd( User::find(1)->image);

Laravel where define query

I'm new in Laravel, I want to know where is the correct place where define functions that query table from DB. In Model or in Controller?
Example:
public function insertUser($firstname, $lastname, $email) {
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->save();
return $user;
}
The function above where I should declare? Models or Controllers?
Edit:
For example: I need to create a function that return male authors that live in USA and their books. I define AuthorController that use Author (Model). What's the right way to define this function? I write a function in my controller that accept gender and nation as arguments, like:
public function getAuthoursByGenderAndNation($gender, $nation) {
$authors = Author::with("books")->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return $authors;
}
Or I define a generic function that returns all authors with their books and then apply where clause on function that call this generic function? Like:
public function showAuthors(Request $request) {
$gender = $request->get("gender");
$nation = $request->get("nation");
$authors = $this->getAuthors()->where("gender", "=", $gender)->where("nation", "=", $nation)->get();
return view("authors", ["authors" => $authors]);
}
public function getAuthors() {
$authors = Author::with("books");
return $authors;
}
keep in mind that all application logics should be in controller, and all data operations should be in model. in your question insert user is a application logic, so you should place that on controller, but if you want to define how data is managed, place that method in model. For example, you want a model has ability to retrieve a collection with some condition, may be a user with female gender only so you can Access it via Modell::getFemale()
The function you mention, should be used within a controller. I would recommend that you get a grasp on how MVC works before you dive in Laravel.
Reading that may be useful to you
MVC Concept
Laravel Docs
PHP MVC Tutorial
As according to MCV recommendations.
M (model) should be fat and C (controller) should be thin.
you should write your all database transaction related code in model. Even you can create repositories for database queries.
Your controller should be thin, so you should write only logical code there, like calling model function.
Example:
UserController.php
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\Request;
class UserController extent Controller {
use App\User;
protected $_user;
public function __construct(User $user) {
$this->_user= $user;
}
function saveUser(Request $request) {
$user->fill($request->all());
$user->save();
// or you can directly save by $user->create($request->all());
}
}
This is how you can directly fill data to your User model with $fillable attribute defined there as
$fillable= ['name','email','password'];
If you define your model under the conventions of Eloquent you can simply use the built in Eloquent methods to insert your user as demonstrated in the documentation.
https://laravel.com/docs/5.3/eloquent#inserting-and-updating-models
In the wider scope of your question: 'where to define functions that query the DB table'.
I would suggest typically defining these on the model and looking to make use of the structures provided by Eloquent, for example defining scoped queries on your model.
The code in your controller would then call methods on your model eg.
Model::create();
It also appears you are trying to insert users. I would strongly suggest you look into using Laravel's built in Authentication structures. You'll find these very powerful.
https://laravel.com/docs/5.3/authentication
Hope this helps get you started.

Laravel Eloquent - Loading query scopes eagerly

I have a data structure in which I need objects to be aware of their needed dependencies for loading.
What I can do
Currently, I can do this to load the first layer of relationships, this is obviously a very basic model:
class Ticket {
public function notes(){}
public function events(){}
public function tags(){}
public function scopeWithAll($query)
{
$query->with('notes', 'events', 'tags');
}
}
// Loads Ticket with all 3 relationships
$ticket = Ticket::withAll();
This works great! The problem being, I need to chain this functionality down to 3-5 levels of dependent relationships. Each of the 3 loaded models is going to have n relationships of its own.
I know I can do this through eager loading if I specify all of the relationship names, as follows:
public function scopeWithAll($query)
{
$query->with('notes.attachments', 'notes.colors', 'events', 'tags', 'tags.colors.', 'tags.users.email');
}
This works great too. But I need my code to be smarter than that.
What I need to do
Statically defining the scope of each object load is not desirable at this point in my project. I need to be able to load a Ticket, and the Ticket load all of its relationships, and each of those relationships load all of their relationships.
The only way I can think to do this is find some way to eagerly load a query scope for each relationship on the class. Something like
public function scopeWithAll($query)
{
$query->with('notes.withAll()', 'events.withAll()', 'tags.withAll()');
}
Is there currently a way to do this within Eloquent?
Maybe you can try something like this:
User::withRelatives()->find(1);
Okay, that's an idea and how to implement that? For example, if you have some related methods for your User model such as 'posts', 'roles' etc then keep all the related methods (methods that make relationship) in a separate trait, for example:
trait UserRelatives {
public function posts()
{
// ...
}
public function roles()
{
// ...
}
}
Now, in the User model you may create a scopeMethod like withAll and inside there you may try something like this:
public function scopeWithAll($query)
{
// Get all the related methods defined in the trait
$relatives = get_class_methods(UserRelatives::class);
return $query->with($relatives);
}
So, if you do something like this:
$user = User::withAll()->find(1);
You'll be able to load all related models. Btw, get_class_methods(UserRelatives::class) will give you an array of all methods defined in that trait which may look something like this:
['posts', 'roles']
So, User::withAll() will load all the related models and then run the query.So, as a result the scope will do something like this:
$query->with(['posts', 'roles']);
Well, this is an abstract idea but hope you got it. Share your idea if you found something better.
Update:
According to your Model and related methods, this may look something like this:
class Ticket {
use TicketRelativesTrait;
public function scopeWithAll($query)
{
$relatives = get_class_methods(TicketRelativesTrait::class);
return $query->with($relatives);
}
}
Trait:
trait TicketRelativesTrait {
public function notes(){}
public function events(){}
public function tags(){}
}
// Loads Ticket with all relationships
$ticket = Ticket::withAll()->find(1);
This is more dynamic, no need to mention the related methods and whenever you add a new relationship method in the trait, that will also be loaded.

How to load models in Laravel?

I started studying Laravel and ran into a problem using models. How to load them? For example in CodeIgniter i used it like $model = $this->load->model('some_model'). In Laravel when i call it from controller like Sites::OfUser() it work fine, but when i call Sites::getId() it says that method should be static...
Is it possible to call method without static or i need to create facades for each model?
My model looks like this:
namespace Models;
use Eloquent;
class Sites extends Eloquent {
public function scopeOfUser($query)
{}
public function getId($name)
{}
}
For static method--
$type = Sites ::scopeOfUser($query);
and if you want normal like codeingiter then use--
$model = new Sites ();
$type = $model->scopeOfUser($query);
You can of course make a static method in the model, and do some static work in it (get ID for name or whatever).
That's no problem.
However, you must declare it static if you want to use the ::, which you are doing not.
public static /* <-- this */ function getId($name)
{
// Do work
// return $result;
}
If you want to access a method with ::, you will need to make it a static method or create a Facade.
The reason why Sites::OfUser() is "working" is because you have prefixed that method with scope.
Scopes allow you to easily re-use query logic in your models. To
define a scope, simply prefix a model method with scope.
If you want to use Facades you can follow my answer here on how to create a Facade.

Categories