Laravel, abstraction layer for 'users' table - php

In a Laravel project I want to use the Laravel authentication table 'users' to have a foreign field key to point to another table as layer of abstraction. Is there a way of forcing the user registration to add a row to that abstraction layer table? Its model is simple there is just one attribute.
My RegisterController:
` protected function create(array $data) {
Rekvirent::create([
'rekvirent' => $data['initialer'],
]);
return User::create([
'initialer' => $data['initialer'],
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
`
I get an error message from mysql that theres a foreign key error suggesting that the rekvirent has not been inserted when it gets to inserting the row in the users table.
My rekvirent model is as follows
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Rekvirent extends Model {
public $timestamps = false; // dumps timestamps from table
public $incrementing = false; // if no autoincrementing
protected $table = 'rekvirent'; // change default snake-case name
protected $keyType = 'string'; // if key is not integer
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'initialer',
];
/*
public function user() {
return $this->hasOne('App\User');
}
*/
}

If you are looking to do certain actions once a new user is registered, you can listen to events.
If you see Illuminate/Foundation/Auth/RegistersUsers.php :
/**
* Handle a registration request for the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
You will notice laravel is internally emitting an event Illuminate\Auth\Events\Registered. You can listen to that event and then do actions you need like inserting into a separate table etc.
See event documentation for listening an event.

Related

Laravel: Foreign id does not have default value

Whenever I run this request POST 0.0.0.0:80/api/clients/1/details?preferred_day=wednesday&preferred_time=08:00:00&goals=get better! in Postman, I get:
SQLSTATE[HY000]: General error: 1364 Field 'client_id' doesn't have a default value (SQL: insert into `client_details` (`preferred_day`, `preferred_time`, `goals`, `updated_at`, `created_at`) values (wednesday, 08:00:00, get better!, 2022-05-01 14:31:51, 2022-05-01 14:31:51))
From my understanding, the ClientDetail model should be defined as below. Notice the relationship and the fact that client_id is fillable.
class ClientDetail extends Model
{
use HasFactory;
protected $guarded = ['id'];
protected $fillable = ['client_id','preferred_day','preferred_time','goals'];
public function client() {
return $this->belongsTo(Client::class,'client_id');
}
}
I have written the Client model as below. Notice that I have the inverse relationship defined as per the docs.
class Client extends Model
{
use HasFactory;
protected $guarded = ['id'];
protected $fillable = ['first_name','middle_name','last_name','email','phone_number','address','unit','postal_code','province','city','country'];
public function detail() {
return $this->hasOne(ClientDetail::class);
}
public function concerns() {
return $this->belongsToMany(Concern::class);
}
}
Finally the migration is defined as below:
public function up()
{
Schema::create('client_details', function (Blueprint $table) {
$table->id();
$table->foreignId('client_id')->constrained();
$table->string('preferred_day')->default('wednesday');
$table->time('preferred_time')->useCurrent();
$table->longText('goals');
$table->timestamps();
});
}
This is functioning as expected because when I look at DB, I see the following (notice how the foreign key references client.id):
I have run dd on the request and I get the result below.
The only thing I can see is that the binding of routes is not working properly. I have included them below. Any assistance troubleshooting this would be appreciated.
EDIT - Added store function as requested
ClientDetailController.php
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
$validator = Validator::make($request->all(), [
'preferred_day' => [
'required',
Rule::in('wednesday','friday'),
],
'preferred_time' => 'required|date_format:h:i:s',
'goals' => 'required|min:10',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$validated = $validator->validated();
$detail = ClientDetail::create($validated);
return new ClientDetailResource($detail);
}
Your issue is in this line:
$detail = ClientDetail::create($validated);
Explanation:
You are creating Client Details without a Client themselves, in other words you must populate client_id with a proper value:
You have to options: just before the wrong line mentioned above:
NOT RECOMMENDED, hard code the client_id just so you can test this. by doing this line $validated['client_id'] = 1 where 1 is example.
Do it properly by adding getting a client then create/store their details.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Client $client, Request $request)
{
$validator = Validator::make($request->all(), [
'preferred_day' => [
'required',
Rule::in('wednesday','friday'),
],
'preferred_time' => 'required|date_format:h:i:s',
'goals' => 'required|min:10',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$validated = $validator->validated();
// $client var is type-hinted in the function signature above
$client->detail()->create($validated);
$detail = ClientDetail::create();
return new ClientDetailResource($detail);
}

Laravel delete linked model's data by user_id in booted function of User model

I've read part of the Laravel docs for events and closures for models, I've got various models in my project whereby a user may have data linked to them in another table by a user_id column, the user_id column that I have in my various tables is structured as an unsigned integer (I'm aware I could've gone with a foreignId column by kind of a legacy approach here)
It looks like:
$table->integer('user_id')->unsigned()->nullable()->index();
I'd like to delete user data by their ID within these other tables and rather than creating a delete function and grabbing each model I want to delete data against, I've utilised the closure booted function and what I believe to be an event to listen and delete related model data, but I experience an error when trying to delete my user account, other data in other tables isn't deleted, the error I get is:
Call to undefined method App\Models\User::releationship()
My user model looks like:
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
use Notifiable, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name', 'last_name', 'email', '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 [];
}
/**
* Route notifications for the Slack channel.
*
* #param \Illuminate\Notifications\Notification $notification
* #return string
*/
public function routeNotificationForSlack($notification)
{
$url = $this->slack_webhook;
$webhook = (isset($url) && !empty($url)) ? $url : null;
return $webhook;
}
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function ($model) {
$model->relationship()->delete();
});
}
}
And an example (of many) model I have, UptimeChecks looks like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class UptimeChecks extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'uptime_checks';
/**
* Join user table
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
All is then kicked off by a deleteAccount function in my API, which is deleting the user's account, but isn't deleting data in other tables. What am I missing and how could I do a check to make sure other data is deleted before confirming to the user that their account and linked data is gone?
/**
* Delete account
*
* #return Response
*/
public function deleteAccount(Request $request)
{
// attempt to delete account
try {
$user = User::findOrFail(Auth::id());
$user->delete();
// everything went okay!
return response()->json(['success' => true, 'message' => 'Your account has been deleted'], 200);
} catch (Exception $e) {
// catch the error
return response()->json(['success' => false, 'message' => 'We was unable to delete your account at this time'], 422);
}
}
In Laravel, when doing $model->relationship()->delete(); you will need to have the relationship defined and relationship() seems like it is copy pasted code snippet. Simply add the relationship to your User model.
class User extends Authenticatable implements JWTSubject, MustVerifyEmail
{
...
public function uptimeChecks() {
return $this->hasMany(UptimeChecks::class);
}
}
Now you can access and delete the relationship in your boot method.
$model->uptimeChecks()->delete();
You need to create a function in User.php
public function uptimeCheck()
{
return $this->hasOne('App\UptimeChecks');
}
and change the boot function
$model->uptimeCheck()->delete();
This way you need to do for all related relations.
This probably should be: $model->user()->delete() instead. There's nothing else.
If this shouldn't be the intention, reconsider the direction of the relationship.

Why is the value of the 'id' column of the database showing null?

I am using laravel 6.I have created a table called 'student', where the value increment of the 'id' column is supposed to happen but is not happening.
This is my migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStudentsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('students', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email');
$table->bigInteger('phone');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('students');
}
}
In my students table:
My StudentController file:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Student;
class StudentController extends Controller
{
public function student()
{
return view('student.create');
}
public function index()
{
$student = Student::all();
return view('student.index', compact('student'));
}
public function store(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|max:50|min:5',
'phone' => 'required|unique:students|max:12min:9',
'email' => 'required|unique:students',
]);
$student = new Student;
$student->name = $request->name;
$student->email = $request->email;
$student->phone = $request->phone;
$student->save();
$notification = array(
'message' => 'Data Insert Done!',
'alert-type' => 'success'
);
return Redirect()->route('all.category')->with($notification);
// DB::table('student')->insert($data)
// return response()->json($student);
}
public function ViewStudent()
{
}
}
Model file:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Student extends Model
{
protected $fillable = [
'id','name', 'email', 'phone',
];
}
There is a possibility that you are working with a database whose schema was set for the students table either manually (not through migration, but, for example, by executing an SQL query where auto-increment was not set), or after applying the migration, the auto-increment was removed.
Because your migration code is written correctly according to the official Laravel documentation for the method increments(string $attribute):
I see two solutions here:
change a table column via SQL query so that it matches the description in the migration
ALTER TABLE students CHANGE id id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY;
or the same using phpmyadmin or IDE tools;
generate a schema using your migration (php artisan migrate --path=database/migrations/..._create_students_table.php), but for this preliminarily you need to save the students table data, for example, to a dump.
Since you are using phpmyadmin, look at the settings for the id attribute in the students table.
The only reason I can think of is if you did something like this in your model:
/**
* Indicates if the IDs are auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
If so then it should be set to true or removed entirely.
Second, make sure your id is guarded in your model like so:
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = ['id'];
This way you avoid mass assignments.
Judging by your controller code, I assume the error lies somewhere in the line where you grab an instance of your student model
Change
$student = new Student;
To
$student = new Student();
You need a new instance of a specific model in order to insert a new id, please post your current model code also.
Sample model code.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'product_bar_code', 'product_name', 'product_image', 'product_price'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
}
Maybe something is wrong with the way you have written your model code.

Laravel 5.4 : Creating 'Profile Class instance' after creating new user

In Laravel 5.4, they hard-coded the user authentication system, So when you use artisan command 'make:auth' everything will be created for you under the hood, but the thing is that i want when my user get registered successfully i want to create a new instance of 'Profile Class' and make the table columns empty until the user fills his profile, So where can I place the code for creating user profile?
In the RegisterController you can override the registered function.
This function is called directly after a user has successfully registered.
/**
* The user has been registered.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function registered(Request $request, $user)
{
// Create a profile here
}
Alternatively, you could also do this with model events directly on the user model
class User extends Authenticatable
{
protected static function boot()
{
parent::boot();
static::creating(function($user) {
// Create profile here
});
}
}
In your app\Http\Controllers\Auth\RegisterController.php on the create() method right after you create a new user you can do this:
use App\Profile; // <-- Import this at the top
protected function create(array $data)
{
$user = User::create([ // <-- change this as you see fit
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
Profile::create(['user_id' => $user->id]);
return $user;
}

Test relations in Laravel 4 with FactoryMuff

I am new to TDD, so excuse me if this is a noob question.
I have Users and Projects, related in 2 different ways:
A Project Belongs to a User
Many Users can be authorized to many Projects.
I am following this tutorial http://net.tutsplus.com/tutorials/php/testing-like-a-boss-in-laravel-models/?search_index=16 , so I am using FactoryMuff.
This is my Project class:
<?php
use LaravelBook\Ardent\Ardent;
class Project extends Ardent
{
/**
* Ardent validation rules
*/
public static $rules = array(
'name' => 'required', // Project Title
'user_id' => 'required|numeric', // User owner id
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'name' => 'string',
'user_id' => 'factory|User', // Will be the id of an existent User.
);
/**
* Belongs to user
*/
public function owner()
{
return $this->belongsTo( 'User', 'user_id');
}
/**
* Many Users can be authorized
*/
public function authorized()
{
return $this->belongsToMany( 'User', 'project_user', 'project_id', 'user_id')->withTimestamps();
}
}
And this is my User class:
<?php
use Zizaco\Entrust\HasRole;
use Zizaco\Confide\ConfideUser;
class User extends ConfideUser {
/**
* Ardent validation rules
*/
public static $rules = array(
'username' => 'required|min:4|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|between:4,16|confirmed',
'password_confirmation' => 'required|between:4,16',
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'username' => 'string',
'email' => 'email',
'password' => '12345',
'password_confirmation' => '12345',
);
(more code)
/**
* Many Users can be authorized on many Projects
*/
public function authorized()
{
return $this->belongsToMany( 'Project', 'project_user', 'user_id', 'project_id')->withTimestamps();
}
/**
* Users can have many Projects
*/
public function projects()
{
return $this->hasMany( 'Project');
}
}
And this is my ProjectTest:
<?php
use Zizaco\FactoryMuff\Facade\FactoryMuff;
use Way\Tests\Factory;
class ProjectTest extends TestCase
{
use Way\Tests\ModelHelpers;
/**
* Test relationship with User
*/
public function testRelationshipWithUser()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$this->assertEquals($project->user_id, $project->owner->id);
}
/**
* Test relationship with Authorized Users
*/
public function testRelationshipWithAuthorizedUsers()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$project->save();
$project->authorized()->attach($project->user_id);
$this->assertEquals($project->user_id, $project->authorized->first()->id);
}
}
If I run the tests individually (commenting the other) both pass. However, if I run both, I get this error:
Caused by
PDOException: SQLSTATE[HY000]: General error: 1 table users has no column named password_confirmation
Why is it complaining about that column in the second test and not in the first? :S
You need to set public $autoPurgeRedundantAttributes = true; on your User model.
Ardent (which Confide extends from) will automatically purge the _confirmation fields, but by default it is set to false.

Categories