Defining many-to-many bidirectional relations more eloquently - laravel

I have implemented the relationship without Eloquent but I was wondering is there was a way to define this relationship in Eloquent so that my application can have more consistency.
table User
-id
-other user attributes
table friend_requests:
-id
-sender_id
-reciever_id
table friends
-id
-first
-second
The friendRequest relation has been easily implemented in the Eloquent but the problem lies in Friends.
If I do this in the User model class:
public function friends(){
return $this->belongsToMany(User::class,'friends','first','second');
}
This wouldn't work as you would have noticed. Let me explain with example:
Table: friends
id | first | second
1 | 1 | 2
2 | 3 | 1
you see that user_1 is friends with user_2 as well as user_3 as the relationship is bi-directional. But Eloquent will naturally return that user_1 is friends with user_2 only. After thinking for a while I tweaked the statement but made little progress'
public function friends(){
return $this->belongsToMany(User::class,'friends','first','second')
->orWhere('second',$this->id);
}
That is because now it selects both rows but the Users it returns are those whose id = second which means that in the second case it will return the user itself.
I implemented the relations with my own methods in User model class which use DB::table('friends')-> to addFriend(User $user), removeFriend(user $user) and returns list of friends(), but I'm disappointed that this isn't as eloquent as Eloquent relationships.
Perhaps some more experienced developers here would have come across this kind of problem and would have dealt better than I did. How do you propose I deal with this problem. Should I stick with my approach or is there a better way to deal with it?

A more manageable way to implement bidirectional relations would be to create two entries for each confirmed friendship.
So a user would make a friend request to another user. When the second user confirms the friend request, two friendships are created.
Example Controller
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\User;
use App\FriendRequest;
use App\Friendship;
class FriendshipController extends Controller
{
public function friendRequest(Request $request)
{
$receiver_id = $request->input('receiver_id');
$request->user()->friend_offers()->create([
'receiver_id' => $receiver_id
]);
}
public function friendshipConfirmation(Request $request)
{
$friend_request_id = $request->input('friend_request_id');
$friend_request = FriendRequest::find($friend_request_id);
$friend_request->receiver->friendships()->create([
'user_2_id' => $friend_request->sender->id
]);
$friend_request->sender->friendships()->create([
'user_2_id' => $friend_request->receiver->id
]);
}
}
Database Tables
(Note the proper spelling of receiver and plural users table)
table users
- id
- other user attributes
table friend_requests:
- id
- sender_id
- receiver_id
table friendships
- id
- user_1_id
- user_2_id
User Model
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable
{
use SoftDeletes;
public $timestamps = true;
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
//
];
/**
* Return friend requests from other users
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function friend_requests()
{
return $this->hasMany(FriendRequest::class, 'receiver_id');
}
/**
* Return friend requests sent to other users
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function friend_offers()
{
return $this->hasMany(FriendRequest::class, 'sender_id');
}
/**
* Return friendships with other users
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function friendships()
{
return $this->hasMany(Friendship::class, 'user_1_id');
}
}
FriendRequest Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class FriendRequest extends Model
{
use SoftDeletes;
public $timestamps = true;
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'sender_id',
'receiver_id'
];
/**
* Return the requesting user
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function sender()
{
return $this->belongsTo(User::class, 'sender_id');
}
/**
* Return the receiving user
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function receiver()
{
return $this->belongsTo(User::class, 'receiver_id');
}
}
Friendship Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Friendship extends Model
{
use SoftDeletes;
public $timestamps = true;
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_1_id',
'user_2_id'
];
/**
* Return user_1
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function first()
{
return $this->belongsTo(User::class, 'user_1_id');
}
/**
* Return user_2
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function second()
{
return $this->belongsTo(User::class, 'user_2_id');
}
}

Related

How can I change the filed email from Larval 8 auth user table to username?

currently, I'm trying to change the field email in user table to username. But seems like it did not work at all. What I did just replace the email (inside of $fillable array) to username.
2.Here is the code for User.php file.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name',
'username',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* #return mixed
*/
public function getJWTIdentifier() {
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims() {
return [];
}
public function username()
{
return 'username';
}
}
Here is the schema for my user table
You also have to update your user table migration. If you don't need the email field, you can replace the line :
$table->string('email')->unique();
with :
$table->string('username');
Then, run :
php artisan migrate:refresh
For this situtation you need to update your migration file for this, then you can use your username without e-mail.

laravel policy gves flalse

I am new to laravel and now trying to learn policies so I created the following models:
class group extends Model
{
protected $fillable = [
'id','book_id',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
public function users(){
return $this->belongsToMany('BOOK_DONATION\User')->withPivot('last_seen_id');
}
}
and :
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email','country','state/provience','city', 'token', 'password','postal_code',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password','verfied', 'remember_token',
];
public function Book(){
return $this->hasMany('App\Book');
}
public function groups(){
return $this->belongsToMAny('BOOK_DONATION\group')->withPivot('last_seen_id');
}
}
and :
class group_user extends Model
{
//
protected $table='group_user';
protected $fillable = [
'user_id','group_id','last_seen_id'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
}
now the group_user is model for the intermidate table required for the many to many relation between users and groups
and here is my policy:
class group_userPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the group_user.
*
* #param \BOOK_DONATION\User $user
* #param \BOOK_DONATION\group_user $groupUser
* #return mixed
*/
public function view(User $user, group_user $groupUser)
{
//
return in_array($user->id,$groupUser->user_id);
}
}
and here is the specific programming lines from controller related :
$grouUser=group_user::where('group_id',$group_id)->pluck('user_id');
if(Auth::user()->cant('joinChat',$grouUser)) return redirect('/sorry');
now I always get redirected to sorry page but I have the right yo join chat so what is wrong?

Appending Eloquent Attributes/Modified Relation Date on Query Building / Code Optimization

I'm looking for a solution to optimize my Code using Laravel Eloquent.
My issue is that I want to add Attributes conditionally, and this Attributes is basically the a transformed many-to-many relationship.
At the moment I have this in my controller (simplified):
<?php
namespace App\Http\Controller;
/**
* Class Category
*/
class Category extends Controller
{
/**
* #return Collection
*/
public function index()
{
return Category::withCount('countries')->get();
}
/**
* #param int $id
*
* #return Category
*/
public function show($id)
{
$result = Category::where('id', $id)
->with('countries')
->firstOrFail();
$result->countries_list = '';
return $result;
}
}
My Category model looks like this (simplified):
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* Class Category
*/
class Category extends Model
{
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'countries',
];
/**
* #return string
*/
public function getCountriesCountAttribute()
{
return trans_choice('labels.countries', $this->original['countries_count']);
}
/**
* #return
*/
public function getCountriesListAttribute()
{
return $this->countries->pluck('alpha_2');
}
/**
* Get the related Countries.
*/
public function countries()
{
return $this->belongsToMany(
Country::class,
'category_country',
'category_id',
'country_id'
);
}
}
The Country Model is just a list of Countries with id, name, the Alpha2 Code, etc. I can't use the protected $appends to add countries_list because than the the list would be always included.
I also can't change my Countries model because this is used in several other occurrences.
What I'm looking for is a way to optimize the code in the controller to this:
<?php
namespace App\Http\Controller;
/**
* #return Collection
*/
public function index()
{
return Category::withCount('countries')->get();
}
/**
* #param int $id
*
* #return Category
*/
public function show($id)
{
return Category::where('id', $id)
->withAttribute('countries_list') // An array of all country aplha 2 codes
->firstOrFail();
}
You can access the countries_list attribute after querying (don't include it in your query).
public function show($id)
{
$category = Category::findOrFail($id);
$list = $category->countries_list; // this calls getCountriesListAttribute()
}

Laravel - Query scopes across models

In a nutshell, I want to create a function that my query scopes can use across multiple models:
public function scopeNormaliseCurrency($query,$targetCurrency) {
return $query->normaliseCurrencyFields(
['cost_per_day','cost_per_week'],
$targetCurrency
);
}
I have got my logic working within this scope function no problem, but I want to make this code available to all my models, as there are multiple currency fields in different tables and I don't want to be replicating the code in each query scope - only specify the columns that need attention.
So, where would I make my function normaliseCurrencyFields? I have extended the Model class as well as used the newCollection keyword to extend Collection but both result in Call to undefined method Illuminate\Database\Query\Builder::normaliseCurrencyFields() errors.
I have looked into Global Scoping but this seems to be localised to a Model.
Am I along the right lines? Should I be targeting Eloquent specifically?
Create an abstract base model that extends eloquent then extend it with the classes you want to have access to it. I do this for searching functions, uuid creation, and class code functions. So that all of my saved models are required to have to certain attributes and access to my searching functions. For instance I created a static search function getobjectbyid(). So that when extended I can call it like so:
$user = User::getobjectbyid('habwiifnbrklsnbbd1938');
Thus way I know I am getting a user object back.
My base model:
<?php
/**
* Created by PhpStorm.
* User: amac
* Date: 6/5/17
* Time: 12:45 AM
*/
namespace App;
use Illuminate\Database\Eloquent\Model as Eloquent;
abstract class Model extends Eloquent
{
protected $guarded = [
'class_code',
'id'
];
public $primaryKey = 'id';
public $incrementing = false;
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
$this->class_code = \App\Enums\EnumClassCode::getValueByKey(get_class($this));
$this->id = $this->class_code . uniqid();
return $this;
}
public static function getObjectById($id){
$class = get_called_class();
$results = $class::find($id);
return $results;
}
public static function getAllObjects(){
$class = get_called_class();
return $class::all();
}
my user model:
<?php
namespace App;
use Mockery\Exception;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use App\Model as Model;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'contact', 'username', 'email_address'
];
/**
* The column name of the "remember me" token.
*
* #var string
*/
protected $rememberTokenName = 'remember_token';
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'remember_token', 'active'
];
/**
* the attributes that should be guarded from Mass Assignment
*
* #var array
*/
protected $guarded = [
'created_at', 'updated_at', 'password_hash'
];
/**
* Define table to be used with this model. It defaults and assumes table names will have an s added to the end.
*for instance App\User table by default would be users
*/
protected $table = "user";
/**
* We have a non incrementing primary key
*
* #var bool
*/
public $incrementing = false;
/**
* relationships
*/
public function contact(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasOne(Contact::class);
}
public function customers(){
// return $this->hasOne(Contact::class, 'id', 'contact_id');
return $this->hasMany(Customer::class);
}
/**
* User constructor.
* #param array $attributes
*/
public function __construct($attributes = array()) {
parent::__construct($attributes); // Eloquent
// Your construct code.
$this->active = 1;
return $this;
}
/**
* #param $password string
* set user password_hash
* #return $this
*/
public function setPassword($password){
// TODO Password Validation
try{
$this->isActive();
$this->password_hash = Hash::make($password);
$this->save();
} catch(\Exception $e) {
dump($e->getMessage());
}
return $this;
}
/**
* Returns whether or not this use is active.
*
* #return bool
*/
public function isActive(){
if($this->active) {
return true;
} else {
Throw new Exception('This user is not active. Therefore you cannot change the password', 409);
}
}
public function getEmailUsername(){
$contact = Contact::getObjectById($this->contact_id);
$email = Email::getObjectById($contact->email_id);
return $email->username_prefix;
}
/**
* #return string
*
* getFullName
* returns concatenated first and last name of user.
*/
public function getFullName(){
return $this->first_name . ' ' . $this->last_name;
}
/**
* Get the name of the unique identifier for the user.
*
* #return string
*/
public function getAuthIdentifierName(){
return $this->getKeyName();
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier(){
return $this->{$this->getAuthIdentifierName()};
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword(){
return $this->password_hash;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken(){
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
}
}
/**
* Set the token value for the "remember me" session.
*
* #param string $value
* #return void
*/
public function setRememberToken($value){
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* Get the column name for the "remember me" token.
*
* #return string
*/
public function getRememberTokenName(){
return $this->rememberTokenName;
}
/**
* Get the e-mail address where password reset links are sent.
*
* #return string
*/
public function getEmailForPasswordReset(){
}
/**
* Send the password reset notification.
*
* #param string $token
* #return void
*/
public function sendPasswordResetNotification($token){
}
public function validateAddress(){
}
}
a TestController:
public function test(){
$user = User::getObjectById('USR594079ca59746');
$customers = array();
foreach ($user->customers as $customer){
$contact = Contact::getObjectById($customer->contact_id);
$name = PersonName::getObjectById($contact->personname_id);
$c = new \stdClass();
$c->id = $customer->id;
$c->name = $name->preferred_name;
$customers[] = $c;
}
$response = response()->json($customers);
return $response;
}
Take note on how getObjectById is extended and available to my other classes that extend my base model. Also I do not have to specify in my user model an 'id' or 'class_code' and when my user model is constructed it calls the parent constructor which is the constructor on my base model that handles 'id' and 'class_code'.

Laravel 5 Querying Relationship

Here is the relationship 1 code:
/**
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function address()
{
return $this->hasMany('App\IPAddress', 'group_id');
}
and relationship 2 code:
/**
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function group()
{
return $this->belongsTo('App\IPGroups');
}
I want to get all ip addresses that belongs to specified group. I don't want to write raw queries, I need to be done with querying relationship. Does anyone has an idea?
I tried to do something like this:
/**
* Get IP Addresses of specified group
* #param Request $request
* #return mixed
*/
public function getIP(Request $request)
{
$group = IPGroups::findOrFail($request->group_id);
return $group->address;
}
but I need to add one where statement where I can pick only active ip addresses.
Here is the model 1 code:
namespace App;
use Illuminate\Database\Eloquent\Model;
class IPGroups extends Model
{
/**
* Working Table
* #var string
*/
protected $table = 'ip_groups';
/**
* Guarded Values From Mass Assignment
* #var array
*/
protected $guarded = [ 'id' ];
/**
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function address()
{
return $this->hasMany('App\IPAddress', 'group_id');
}
}
and the second model code:
namespace App;
use Illuminate\Database\Eloquent\Model;
class IPAddress extends Model
{
/**
* Working Table
* #var string
*/
protected $table = 'ips';
/**
* Protected Values From Mass Assignment
* #var array
*/
protected $fillable = [ 'group_id', 'ip', 'description', 'status' ];
/**
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function group()
{
return $this->belongsTo('App\IPGroups');
}
}
Try this, getting only the addresses with status as 'Active':
return $group->address->where('status','Active');
The reason this doesn't work:
return $group->address->where('status','=','Active');
is that the where we are using here is the where of the class Collection, which doesn't accept a comparator as second parameter as the where of the Models do.

Resources