Okay so I'm building a pretty large application in Laravel. Users manage their own virtual soccer teams. I have a users table then I have a teams table with team specefic things like name, level,and arena, etc. For the arena though I decided to add a arenas table and then add a arena_id column in the teams table instead of just adding the arena name to the teams table.
so here is the basic relantionship:
User hasOne Team
Team hasOne User
Team hasOne Arena
Arena hasOne Team
so if I wanted to get the arena for a user I call the method
$user = User::with('team')->where('username', '=', $username)->first();
$user->team->arena->arena_name;
and everything works fine; however I feel there is a much cleaner or simpler way of doing this. Is there or is this fine for the aplication?
There is nothing wrong with the way you are doing it. That is a perfectly good way of doing it for your needs. However something that might help is creating a getArenaFromUsername() method in the User model. Your User model would look something like this:
<?php
class User extends \Eloquent {
protected $fillable = [];
public function getArenaFromUsername($username)
{
$user = User::with('team')->where('username', '=', $username)->first();
return $user->team->arena->arena_name;
}
}
So then to get the arena name from a controller you just do:
$user = new User;
$arena = $user->getArenaFromUsername($username);
-----------------------------------------OR-----------------------------------------------
Or use dependency injection by doing the following in your controller using the same method we just created in the model:
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
then to use it you can use one line in any method in your controller like so:
$this->user->getArenaFromUsername($username);
These are all different ways of abstracting your query to make it more reusable and cleaner to call in your controller. Don't be afraid to make public methods in your model to call.
A couple things.
You can eager load the sub-relationship like so:
User::with('team', 'team.arena')...
You can also create an accessor function (http://laravel.com/docs/eloquent#accessors-and-mutators) on your User model to make it a first-class property on the User object:
// accessed via $user->arena
public function getArenaAttribute {
return $this->team->arena;
}
// accessed via $user->arenaName
public function getArenaNameAttribute {
return $this->team->arena->arena_name;
}
Related
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.
I know that hasOneThrough has been introduced to the latest Laravel build, however I can't really upgrade right now. What is the best way to do this?
I have got a users table, which I can't change the structure off, but I need to assign a role to these users, so I have created a pivot table, I want to add a method to my User model to get the role, and the user can only have ONE.
Tables:
users user_roles user_assigned_roles
I could use hasManyThrough, but this would expect many and I want to return a single model rather than a collection .
You could manage this with a combination of one-to-one & one-to-many relationships, and then access the role through an accessor:
`users` 1 --- 1 `user_assigned_roles` m ---- 1 `user_roles`
So, in a UserAssignedRole model:
/** UserAssignedRole.php */
public function role()
{
return $this->belongsTo('App\UserRole');
}
Then in your User model:
/** User.php */
public function assigned_role()
{
return $this->hasOne('App\UserAssignedRole');
}
// defining an accessor for your role:
public function getRoleAttribute()
{
return $this->assigned_role->role; // <--- Access the role of 'UserAssignedRole'
}
So in your controller (or wherever you want) you could do:
/** UsersController.php */
public function myFunction()
{
$user = User::find(1);
$role = $user->role; // <--
dd($role->name);
//
}
PS1: I strongly suggest you to upgrade to the latest version of Laravel to make use of the new features and also for security reasons, fixes etc.
PS2: There is package called Eloquent Has Many Deep by Jonas Staudenmeir that manages this kind of relationship (and more) for you.
I have default user model. Now I have another table for Administrators. Is there a way to return user fields (since admin is extending user) from admin model ? I found this example but its when admin_id is present in user model. I have one-to-one relation here.
class Admin extends User
{
protected $table = 'users';
public static function boot()
{
parent::boot();
static::addGlobalScope(function ($query) {
$query->where('is_admin', true);
});
}
}
This is the example I found. I'm not sure how can I return user fields from my admin model when its on different table.
The point is I want to be able to do something like this (call methods from users):
Admin::first()->posts()
Where posts method is not on Admin class but on user class.
Edit:
To explain it better. I have two tables, users and admins. They are connected in one-to-one relationship so I can do something like this:
$admin = Admin::first();
$posts = $admin->user()->posts();
but since Admin should have all fields from users table and one more field from admins table I'm looking for a way to do this:
$admin = Admin::first();
$posts = $admin->posts();
I don't want to add admin or something to users table, I still want to use admins table since I will need more fields there later.
If both tables have an equal id, use a trait to define your relationships:
trait UserRelationships {
public function posts() {
return $this->hasMany(Post::class, 'user_id');
}
}
class Admin {
use UserRelationships;
}
class User {
use UserRelationships;
}
You'll just have to be sure to explicitly declare the foreign key name in the relationship.
You could also extend the User model and override the $table property but this may present problems for various reasons since User properties exist on the user relationship and not on the Admin model.
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.
Background
I have an internationalized DB that stores its strings for different languages like this:
products
id
price
product_translation
id
product_id
language_id
name
description
languages
id
name (e.g. 'English', 'German')
code (e.g. 'en', 'de')
With appropriate Models for each table (Product, ProductTranslation, Language). In my views I want to fetch a list of products like this:
// get first 20 products, list name and price.
#foreach(Product::take(20)->get() as $product)
{{$product->translations->name}} {{$product->price}}
#endforeach
Problem
My app will return product names according to what the current App::getLocale() is set to (i.e. en and de).
I'm just starting out with Laravel's Eloquent, I'm unsure how to specify the correct relationships (or if I'm actually doing it correctly at all).
My attempt
I have specified a OneToMany relationship in between Product and ProductTranslation:
class Product extends \Eloquent {
protected $with = ['translations'];
public function translations()
{
return $this->hasMany('ProductTranslation');
}
}
This works fine but will return all the translations (we only want the current locale).
I then specify a OneToOne relationship between ProductTranslation and Language:
class ProductTranslation extends \Eloquent {
protected $with = ['language'];
public function language()
{
return $this->hasOne('Language')
->where('code', App::getLocale());
}
}
I know this doesn't work and I am stumped at what to do next. Does anyone have a cleaner approach?
class ProductTranslation extends \Eloquent {
protected $with = ['language'];
public function language()
{
return $this->hasOne('Language');
}
}
In route or controller
ProductTranslation::language()->where('code', '=', App::getLocale())->get();
To keep this in the model do this
public static function getLocale()
{
return static::language()->where('code', '=', App::getLocale())->get();;
}
Call the function using ProductTranslation::getLocale()
Laravel has built-in system for translations and with a little work you can make it work with the database, however that is probably not what it was designed for.
You can't fetch the ones you want cause relationships are based on ids (foreign keys) and not for string constraints or similar.
In your view you could look into filtering out the ones that are not for the language code you wanted using filter(): http://laravel.com/docs/4.2/eloquent#collections
Or You could consider moving the translations to the proper place as hard-coded: http://laravel.com/docs/4.2/localization and if that is not possible you could look into fetching the translations from database still using the method described by that link. Eventually it just returns the array of the translations and does not care how did you build the array, hard-coded or from database.