I'm trying to expand the User Model with another Table (profile) to get a profile-picture, position, etc.
Can I override the index() function of the User Model to do that?
Current Model-Code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
'name',
'email',
'password',
'user_group'
];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
What you are trying to do is setting up a relationship between the User Model and a new Profile Model. To do this you first need to create a Model Profile and it's associated Tabble profiles
php artisan make:model Profile --migration
In database\migrations there should be a file called something like that 2022_11_28_223831_create_profiles_table.php
Now you need to add a foreign key which indicates to which User this profile belongs.
public function up()
{
Schema::create('profiles', function (Blueprint $table) {
$table->id();
// $table->string('path_to_picture')
// user id
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
Now in your User Model add the following function
public function profile()
{
return $this->hasOne(Profile::class);
}
And in your Profile Model
public function user()
{
return $this->belongsTo(User::class);
}
Run php artisan migrate and everything should work as expected
If you want to test if the relationship works as expected create a new TestCase with
php artisan make:test ProfileUserRelationTest
In tests\Feature\ProfileUserRelationTest.php
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\User;
use App\Models\Profile;
use Illuminate\Support\Facades\Hash;
class ProfileUserRelationTest extends TestCase
{
use RefreshDatabase;
public function test_the_relation_between_user_and_profile_works()
{
$user = User::create([
'name' => 'John Doe',
'email' => 'jd#example.com',
'password' => Hash::make('password'),
]);
$profile = new Profile();
$profile->user_id = $user->id;
$profile->save();
$this->assertEquals($user->id, $profile->user->id);
$this->assertEquals($user->name, $profile->user->name);
$this->assertEquals($profile->id, $user->profile->id);
}
}
Now you can run php artisan test to see if everything works.
Be carefull this will refresh your database! So don't test in production.
Output should something like this
PASS Tests\Unit\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the application returns a successful response
PASS Tests\Feature\ProfileUserRelationTest
✓ the relation between user and profile works
Tests: 3 passed
Time: 0.35s
Learn more about Relationships in Laravel: https://laravel.com/docs/9.x/eloquent-relationships
Learn more about migrations: https://laravel.com/docs/9.x/migrations
Alternative
$user = User::create([
'name' => 'John Doe',
'email' => 'jd#example.com',
'password' => Hash::make('password'),
]);
$user->profile()->create(...); // replace the ... with the things you want to insert you dont need to add the user_id since it will automatically added it. It will still work like the one above.
Related
I am working on a hybrid app build with Laravel and Vue.
I have a use case where not all users have certain relations. For example a client can have a Domain and Multiple Business Units.
Currently i have set it up like this:
<?php
namespace App\Models;
use Laravel\Sanctum\HasApiTokens;
use Spatie\MediaLibrary\HasMedia;
use Illuminate\Notifications\Notifiable;
use Lab404\Impersonate\Models\Impersonate;
use Spatie\MediaLibrary\InteractsWithMedia;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements HasMedia
{
use Traits\BaseModelTrait;
use Traits\ActiveTrait;
use InteractsWithMedia;
use Impersonate;
use HasApiTokens;
use Notifiable;
use HasFactory;
protected $hidden = [
'password', 'remember_token',
];
protected $fillable = [
'name', 'email', 'password', 'avatar',
];
protected $casts = [
'settings' => AsArrayObject::class,
'is_admin' => 'boolean',
];
protected $with = [
'domain',
'BusinessUnits'
];
public function scopeAdmin($query)
{
return $query->where('is_admin', true);
}
public function scopeEmployee($query)
{
return $query->whereNull('domain_id');
}
public function scopeClient($query)
{
return $query->whereNotNull('domain_id');
}
public function BusinessUnits()
{
return $this->belongsToMany(BusinessUnit::class, 'users_business_units_pivot');
}
public function Domain()
{
return $this->belongsTo(Domain::class);
}
}
The "problem" with this approach is that for every request 2 queries are executed for each user. I want the relations eager loaded only if the "domain_id" is not null (scopeClient).
For normal "models" i can select per page what models should be loaded etc., but for the authenticated user this is not really possible as i know.
I think i am looking for something like this:
protected $with = [
(!$this->domain_id) ? 'domain' : null,
(!$this->domain_id) ? 'BusinessUnits' : null
];
This currently generates an error: "Constant expression contains invalid operations."
Any advice and or ideas to tackle this would be appreciated!
You can try using events:
// this code should be inside your model
public static function boot()
{
parent::boot();
self::retrieved(function($model){
if($model->domain_id !== null)
{
$model->load('domain', 'BusinessUnits');
}
});
}
and obviously, you have to remove those relations from $with
To get all the user that has domains, use whereHas()
$users = User::whereHas('Domain')->with(['Domain', 'BusinessUnits'])->get();
it will lauch 3 queries, one for the users, one for the domains and one for the business units.
OK so my User models uses webpatser/laravel-uuid. All migrations are using UUID.
So now my model looks like:
<?php
namespace App\Models;
use App\Models\Traits\Uuid;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use Notifiable;
use Uuid;
public $incrementing = false;
public $timestamps = true;
protected $guarded = [
'uuid',
];
protected $keyType = 'string';
protected $primaryKey = 'uuid';
protected $table = 'users';
protected $dates = [
'created_at',
'updated_at',
];
protected $hidden = [
'password',
'remember_token',
];
public function setPasswordAttribute($password): void
{
$this->attributes['password'] = Hash::make($password);
}
}
I want to use database session driver. I created session table via php artisan session:table. All migrations are done. I obviously had to rename existing user_id column. I've changed it to user_uuid. I know it's not enough as I can't find the logic responsible for populating this db table. I guess it's somewhere in the vendor (Illuminate).
Where is the logic to populate my non-default session column?
Now each open the page gives:
So I know what's the issue, what's causing it, how to change it, but I don't know where to start. Thanks for any hints.
I think you would benefit of a custom session handler because the name of the column user_id is hardcoded into the addUserInformation() method.
Extend the existing DatabaseSessionHandler.php and replace the addUserInformation() method so it looks for the correct column name:
class DatabaseUuidSessionHandler extends DatabaseSessionHandler
{
protected function addUserInformation(&$payload)
{
if ($this->container->bound(Guard::class)) {
$payload['user_uuid'] = $this->userId();
}
return $this;
}
}
Register it in one of your service providers:
class SessionServiceProvider extends ServiceProvider
{
public function boot()
{
Session::extend('databaseUuid', function ($app) {
return new DatabaseUuidSessionHandler;
});
}
}
Finally update SESSION_DRIVER in your .env to use the newly created databaseUuid driver.
Remember that this is untested code and should only be used as a guideline of how this could work.
I want to create an audit trail in my model. I already installed owen-it/laravel-auditing package via Composer. My question is that how can I implement it in my Model or controller. Please see my code for controller and Model below. Thanks
My Controller :
<?php
namespace App\Http\Controllers;
use App\Events\Test;
use App\Letter;
use App\Notifications\LetterNotification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Validator;
class LetterController extends Controller
{
public function viewLetter()
{
return view('welcome');
}
/**
* Saves email into database
*
* #param array $data
* #return Letter
*/
protected function create(array $data)
{
$letter = Letter::create([
'email' => $data['email']
]);
$this->letterNotify($letter);
return $letter;
}
/**
* Validates email
*/
public function createLetter(Request $request)
{
$this->validate($request,[
'email' => 'required|email|max:255|unique:letters'
],[
'email.required' => 'Email is required.',
'email.unique' => 'Already registered.',
'email.email' => 'Please put a valid Email address'
]);
$this->create($request->all());
return redirect('/')->with('info','You are now registered.');
}
protected function letterNotify($letter)
{`enter code here`
Notification::send($letter, new LetterNotification($letter));
}
}
For my Model:
<?php
namespace App;
use OwenIt\Auditing\Auditable;
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Letter extends Model implements AuditableContract
{
use Notifiable;
use Auditable;
protected $fillable = ['email'];
protected $table = 'letters';
}
Like I stated in my comment, the Laravel Auditing package only triggers an audit on a database operation involving an Eloquent model and event (by default, created, updated, deleted, restored).
Having said that, here's a list of steps to create an audit when logging in/out:
Create a listener for the Illuminate\Auth\Events\Login event;
Once fired, update a column in the users table that keeps track of the latest login date/time (latest_login_at, for example);
(Optional) update a column with the previous login date/time (last_login_at, for example);
By doing those updates to the users table, the Auditor kicks in;
You can also listen for the OwenIt\Auditing\Events\Auditing or OwenIt\Auditing\Events\Audited events and apply more logic if needed;
Follow the same steps for the Illuminate\Auth\Events\Logout event;
Hey guys I'm trying to learn PHP frameworks as well as OOP and I'm using Laravel 5.1 LTS.
I have the following code in my AuthController
<?php
namespace App\Http\Controllers\Auth;
use App\Verification;
use Mail;
use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
private $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
protected function create(array $data){
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
// generate our UUID confirmation_code
mt_srand((double)microtime()*15000);//optional for php 4.2.0 and up.
$charid = strtoupper(md5(uniqid(rand(), true)));
$uuid = substr($charid, 0, 8)
.substr($charid, 8, 4)
.substr($charid,12, 4)
.substr($charid,16, 4)
.substr($charid,20,12);
$data['confirmation_code'] = $uuid;
// pass everything to the model here
$setVerification = new Verification();
$setVerification->setVerificationCode($data['email'], $data['confirmation_code']);
// send email for confirmation
Mail::send('email.test', $data, function ($m) use ($data){
$m->from('test#test.com', 'Your Application');
$m->to($data['email'])->subject('Thanks for register! Dont forget to confirm your email address');
});
return $user;
}
}
my error message Class 'Models\Verification' not found is coming from this piece of code here
// pass everything to the model here
$setVerification = new Verification();
$setVerification->setVerificationCode($data['email'], $data['confirmation_code']);
which looks right to my beginner's eyes, but it's clearly wrong.
Here is my Verification class that has the setVerificationCode method
<?php
namespace App\Http\Controllers;
use App\User;
use DB;
use App\Http\Controllers\Controller;
class Verification {
/**
* This method will update the confirmation_code column with the UUID
* return boolean
**/
protected function setVerificationCode($email, $uuid) {
$this->email = $email;
$this->uuid = $uuid;
// check to see if $email & $uuid is set
if (isset($email) && isset($uuid)) {
DB::table('users')
->where('email', $email)
->update(['confirmation_code' => $uuid]);
return TRUE;
} else {
return FALSE;
}
}
/**
* This method will validate if the UUID sent in the email matches with the one stored in the DB
* return boolean
**/
protected function verifyConfirmationCode() {
}
}
Please give the following in AuthController
use App\Http\Controllers\Verification;
instead of
use App\Verification;
If we give use App\Verification , it will check if there is any model named Verification.
its seems that, you are missing something, which, Extend your Model with eloquent model
use Illuminate\Database\Eloquent\Model;
class Verification extends Model
{
and the rest is seems fine.
also share your verification model code
Updated
instead of your this line
use App\Verification;
do this
use App\Models\Verification;
as you created custom directory for your Models then its better to auto load it in your composer.json file. add this line "app/Models" in your "autoload" section. follow this
"autoload": {
"classmap": [
"database",
"app/Models"
],
and after that, run this command in your project repo composer dump-autoload
In code with the comment "THIS IS NOT WORKING", I can't authorize.
I get user object and use Auth::login($user) but it doesn't work.
User in this moment creates in database.
public function login(Request $request)
{
if(Auth::check()) {
return redirect('home');
}
if($request->isMethod('post')) {
if($request->has('username') && $request->has('password')) {
$inputs = $request->except('_token');
if(!Auth::attempt(['username' => $inputs['username'], 'password' => $inputs['password']])) {
$user = $this->registerUser($inputs);
if($user) {
Auth::login($user);
}
}
}
return (Auth::check()) ? redirect()->route('home') : redirect()->back()->with('form_error', true);
}
return view('pages.auth.login');
}
User.php maybe here is something wrong?
namespace App\Models\system;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword;
protected $primaryKey = 'uid';
protected $table = 'eltk.dbo.system_users';
protected $guarded = [];
protected $hidden = ['password', 'remember_token'];
}
Let's start step by step.
Why did you create user on a method where you need to handle just login logic?
What's the name of your database? Is it eltk or dbo or what? Define it in config/database.php under 'database' or in .env and use in model just table name, not name of the whole table, is not necessary.
In login method you need something like this:
public function processLogin(Request $request){
if(Auth::attempt(['email' => $request->input('email'), 'password' => $request->input('password'), 'active' => 1])){
return redirect('/');
}else{
return redirect('login')->withMessage('User with this email and/or password does not exist or your account is not active.');
}
}
And btw, when you in if-else statement login user afterward you need to redirect him on some route.