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);
}
Related
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.
I am currently working on my first laravel project and I am facing a problem.
If you have experience with laravel you probably know that by calling php artisan make:auth you will get a predefined mechanism that handles login and registration.
This mechanism is set to understand a couple of commonly used words in order to automate the whole procedure.
The problem that occurs in my case is that I am using oracle db and it won't let me have a table column with the name of password because its a system keyword and it throws errors when trying to insert a user.
So far, I have tried to change my password column to passwd and it worked in my registration form as expected. The User row was successfully inserted and my page was redirected to /home.
But when I try to logout and then relogin, I get this error telling me that my credentials are not correct:
As for my code, I have changed my RegisterController.php so that it takes username instead of email
protected function validator(array $data)
{
return Validator::make($data, [
'username' => 'required|max:50|unique:ECON_USERS',
'passwd' => 'required|min:6|confirmed',
]);
}
protected function create(array $data)
{
return User::create([
'username' => $data['username'],
'passwd' => bcrypt($data['passwd'])
]);
}
The User $fillable
protected $fillable = [
'username', 'passwd'
];
I am guessing that Auth is trying to authenticate with email and not username or that Auth is searching for password and not passwd.
For having username instead of email, you can overwrite username() in your LoginController.php
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'username';
}
And for passwd instead of password, you can do define an accessor in your App\User.php
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->passwd;
}
login.blade.php : Replace email input with username but do not change the name of the input for password.
Use this. It's work for me.
So far I have changed the User.php
public function getAuthPassword(){
return $this->senha;
}
and
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
Also on LoginController.php
public function username()
{
return 'usuario';
}
In the app/Http/Controllers/Auth/LoginController override the default class by adding:
/**
* Validate the user login request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => 'required', 'passwd' => 'required',
]);
}
Don't forget to add use Illuminate\Http\Request;
It could be you have to add this too to your LoginController.
/**
* Get the needed authorization credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only($this->username(), 'passwd');
}
That should do it.
In Laravel 5.7 beside above answer you must change EloquentUserProvider class. search in the file for 'password' word in lines (107,117, and 140) you found 'password' word and change it with new name, and this is all solustion.
In User Model add this method :
public function getAuthPassword(){
return $this->new_password_name;
}
In LoginController add this :
protected function validateLogin(Request $request){
$this->validate($request, [
$this->username() => 'required',
'new_password_name' => 'required',
]);
}
protected function credentials(Request $request)
{
return $request->only($this->username(), 'new_password_name');
}
public function username()
{
return 'new_username';//or new email name if you changed
}
In login.blade.php change id and name of html element.
In EloquentUserProvider class inside validateCredentials and retrieveByCredentials function change 'password' word with the new name.
Edit :I change EloquentUserProvider class but if you think changing laravel class is a bad practice you can create custom provider and override the retrieveByCredentials and validateCredentials functions in the custom provider.
If you are using the latest version. I am returning it like the following code below for a custom password field. On my end, I am using Lumen 6.x though it would apply to current version of Laravel also.
/**
* Get the custom password field for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->custom_password_field_here;
}
IN Custom Controller
public function login(Request $request){
if (Auth::attempt(['email' => $request->email,'password' => $request->password], false)){
return redirect()->intended(route('subportal.dashboard'));
}
return $this->sendFailedLoginResponse($request);
}
protected function validateLogin(Request $request)
{
$this->validate($request, [
$this->username() => 'required', 'password' => 'required',
]);
}
/**
* Get the login username to be used by the controller.
*
* #return string
*/
public function username()
{
return 'email';
}
In App/Users.php
public $table = "customer";
protected $primaryKey = 'cust_id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'pass', 'email','record_date',
];
public function getAuthPassword() {
return $this->pass;
}
In your AuthController.php (Located in app/http/Controllers/Auth)
public function postLogin(Request $request)
{
$this->validate($request, [
'username' => 'required',
'password' => 'required',
]);
$credentials = ($request->only('username', 'password'));
if ($this->auth->attempt($credentials)) {
//do something, credentials is correct!
}
return "Ops! snap! seems like you provide an invalid login credentials";
}
I have model like this
class test extends Model
{
public $rules = [
'title' => 'required',
'name' => 'required',
];
protected $fillable = ['title','name'];
}
And controller like this
public function store(Request $request)
{
$test=new test; /// create model object
$validator = Validator::make($request->all(), [
$test->rules
]);
if ($validator->fails()) {
return view('test')->withErrors($validator)
}
test::create($request->all());
}
Validation show error like this
The 0 field is required.
I want show this
The name field is required.
The title field is required.
I solve it
public function store(Request $request)
{
$test=new test; /// create model object
$validator = Validator::make($request->all(),$test->rules);
if ($validator->fails()) {
return view('test')->withErrors($validator)
}
test::create($request->all());
}
You are doing it the wrong way. The rules array should either be in your controller or better in a Form Request.
Let me show you a better approach:
Create a new Form Request file with php artisan make:request TestRequest.
Example TestRequest class:
namespace App\Http\Requests;
use App\Http\Requests\Request;
class TestRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation messages.
*
* #return array
*/
public function messages()
{
return [
'title.required' => 'A title is required.',
'name.required' => 'The name field is required'
];
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required',
'name' => 'required',
];
}
}
Inject the request object into your controller method.
public function store(TestRequest $request)
{
// You don't need any validation, this is already done
test::create($request->all());
}
You could also look at validating in your model and throwing a ValidationException which will be handled as usual in your controller (with the error bag etc). E.g:
abstract class BaseModel extends Model implements ModelInterface {
protected $validationRules = [];
/**
* Validate model against rules
* #param array $rules optional array of validation rules. If not passed will validate against object's current values
* #throws ValidationException if validation fails. Used for displaying errors in view
*/
public function validate($rules=[]) {
if (empty($rules))
$rules = $this->toArray();
$validator = \Validator::make($rules,$this->validationRules);
if ($validator->fails())
throw new ValidationException($validator);
}
/**
* Attempt to validate input, if successful fill this object
* #param array $inputArray associative array of values for this object to validate against and fill this object
* #throws ValidationException if validation fails. Used for displaying errors in view
*/
public function validateAndFill($inputArray) {
// must validate input before injecting into model
$this->validate($inputArray);
$this->fill($inputArray);
}
}
Then in my Controller:
public function store(Request $request) {
$person = $this->personService->create($request->input());
return redirect()->route('people.index', $person)->with('status', $person->first_name.' has been saved');
}
Finally in my base service class
abstract class BaseResourceService {
protected $dataService;
protected $modelClassName;
/**
* Create a resource
* #param array $inputArray of key value pairs of this object to create
* #returns $object
*/
public function create($inputArray) {
try {
$arr = $inputArray;
$object = new $this->modelClassName();
$object->validateAndFill($arr);
$this->dataService->create($object);
return $object;
}
catch (Exception $exception) {
$this->handleError($exception);
}
}
If the model validates it continues as usual. If there's a validation error it goes back to the previous page with the validation errors in the flash data/error bag.
I will most probably move the $person->validate() method to my service class, however it will still work as outlined above.
You can simply make your validation by writing in Model.
In your Model File
i.e. Models\Test.php
public static $createRules = [
'name'=>'required|max:111',
'email'=>'required|email|unique:users',
];
In Controller
public function store(Request $request)
{
$request->validate(ModalName::$createRules);
$data = new ModelName();
}
Just do this. Everything will be fine.
I am trying to authenticate users in my Laravel application.
I am encountering the following problem:
using driver database in auth.php: I can login using auth::attempt(), and auth::check is working, but I can't validate if the logged in user has a certain role.
using driver eloquent in auth.php: I can login using auth::attempt(), but auth::check is not working. I can however check the role of the logged in user.
edit (question): How can I fix this so that with only one of the drivers, i can do a complete authentication and role check?
Migration tables:
Schema::create('users', function ($table) {
$table->increments('id');
$table->integer('group_id')->unsigned();
$table->string('name', 64);
$table->string('email', 64)->unique();
$table->string('username', 64)->unique();
$table->string('phone', 13);
$table->string('address', 64);
$table->boolean('isresponsible');
$table->string('password', 64);
$table->rememberToken()->nullable();
});
Schema::create('roles', function ($table) {
$table->increments('id');
$table->string('name');
});
Schema::create('users_roles', function ($table) {
$table->integer('user_id')->unsigned();
$table->integer('role_id')->unsigned();
}
);
Schema::table('users_roles', function($table){
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('role_id')->references('id')->on('roles');
});
model class User
<?php
use Illuminate\Auth\UserTrait;`
use Illuminate\Auth\UserInterface;`
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
public $timestamps = false;
public static $rules = ['name' => 'required', 'group_id' => 'required', 'email' => 'required', 'phone' => 'required'];
protected $fillable = ['name', 'group_id', 'email', 'phone', 'address', 'isresponsible', 'password'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
public function group()
{
return $this->belongsTo('Group');
}
public function userroles(){
return $this->hasMany('Userrole');
}
public function roles()
{
return $this->belongsToMany('Role', 'users_roles');
}
public function hasRole($check)
{
dd($this->roles->toArray());
return in_array($check, array_fetch($this->roles->toArray(), 'name'));
}
public function setBasicPassword($id){
$user = User::find($id);
$user->password = Hash::make('changeme');
$user->save();
}
public function isValid()
{
$validation = Validator::make($this->attributes, static::$rules);
if ($validation->passes()) return true;
$this->messages = $validation->messages();
return false;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
// TODO: Implement getReminderEmail() method.
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->email;
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
}
model Class Role
class Role extends Eloquent
{
protected $table = 'roles';
public $timestamps = false;
public static $rules = ['role_id' => 'required', 'name' => 'required'];
protected $fillable = ['name'];
/**
* Get users with a certain role
*/
public function userroles()
{
return $this->belongsToMany('User', 'users_roles');
}
}
HomeController authentication function
public function authenticate(){
$rules = array(
'email' => 'required|email',
'password' => 'required|alphaNum|min:3'
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('login')
->withErrors($validator)
->withInput(Input::except('password'));
} else {
$userdata = array(
'email' => Input::get('email'),
'password' => Input::get('password')
);
if (Auth::attempt($userdata, true)) {
return Redirect::action('HomeController#index');
} else {
return Redirect::action('HomeController#login')->withInput();
}
}
}
USING THE DATABASE DRIVER
- auth:attempt() and auth::check are working
$this->beforeFilter('admin', ['only' => ['index']]); //filter in controller
//filter in filters;php
Route::filter('admin', function()
{
if(!Auth::check()) return Redirect::action('HomeController#index');
if(!Auth::user()->hasRole('admin')) return View::make('errors.401');
});
This fails with 'Call to undefined method Illuminate\Auth\GenericUser::hasRole()'
EDIT The database driver return a GenericUser Object, and I need my own User object. Don't know where I can change this.
Workaround:I'd rather not use this, ugly code and filters (or views) should not need to do this
Route::filter('admin', function()
{
if(!Auth::check()) return Redirect::action('HomeController#index');
$user = User::find((Auth::user()->id));
if(!$user->hasRole('admin')){ return View::make('errors.401');}
});
USING THE ELOQUENT DRIVER
auth::attempt() succeeds
auth::check() fails
no error on the filter
The problem is your implementation of getAuthIdentifier(). This method should actually return the primary key of your table and not the username that's used for logging in.
So yours should look like this:
public function getAuthIdentifier(){
return $this->id;
}
Or actually, I recommend you clean up your model a bit more since all of the getSomeAuthStuff methods are implemented in the two traits.
Use the default model on github as a base and add all your custom code (roles methods, rules etc)
Background info
The value returned from getAuthIdentifier() will be stored in the session.
When using check() afterwards, retrieveById will be called on the UserProvider. And the EloquentUserProvider does this:
public function retrieveById($identifier)
{
return $this->createModel()->newQuery()->find($identifier);
}
It uses find() which searches for the model by it's primary key (usually id)
I am trying to authenticate users in my Laravel application.
I am encountering the following problem:
using driver database in auth.php: I can login using auth::attempt(), and auth::check is working, but I can't validate if the logged in user has a certain role.
using driver eloquent in auth.php: I can login using auth::attempt(), but auth::check is not working. I can however check the role of the logged in user.
edit (question): How can I fix this so that with only one of the drivers, i can do a complete authentication and role check?
Migration tables:
Schema::create('users', function ($table) {
$table->increments('id');
$table->integer('group_id')->unsigned();
$table->string('name', 64);
$table->string('email', 64)->unique();
$table->string('username', 64)->unique();
$table->string('phone', 13);
$table->string('address', 64);
$table->boolean('isresponsible');
$table->string('password', 64);
$table->rememberToken()->nullable();
});
Schema::create('roles', function ($table) {
$table->increments('id');
$table->string('name');
});
Schema::create('users_roles', function ($table) {
$table->integer('user_id')->unsigned();
$table->integer('role_id')->unsigned();
}
);
Schema::table('users_roles', function($table){
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('role_id')->references('id')->on('roles');
});
model class User
<?php
use Illuminate\Auth\UserTrait;`
use Illuminate\Auth\UserInterface;`
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
public $timestamps = false;
public static $rules = ['name' => 'required', 'group_id' => 'required', 'email' => 'required', 'phone' => 'required'];
protected $fillable = ['name', 'group_id', 'email', 'phone', 'address', 'isresponsible', 'password'];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
public function group()
{
return $this->belongsTo('Group');
}
public function userroles(){
return $this->hasMany('Userrole');
}
public function roles()
{
return $this->belongsToMany('Role', 'users_roles');
}
public function hasRole($check)
{
dd($this->roles->toArray());
return in_array($check, array_fetch($this->roles->toArray(), 'name'));
}
public function setBasicPassword($id){
$user = User::find($id);
$user->password = Hash::make('changeme');
$user->save();
}
public function isValid()
{
$validation = Validator::make($this->attributes, static::$rules);
if ($validation->passes()) return true;
$this->messages = $validation->messages();
return false;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
// TODO: Implement getReminderEmail() method.
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->email;
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken()
{
return $this->remember_token;
}
public function setRememberToken($value)
{
$this->remember_token = $value;
}
public function getRememberTokenName()
{
return 'remember_token';
}
}
model Class Role
class Role extends Eloquent
{
protected $table = 'roles';
public $timestamps = false;
public static $rules = ['role_id' => 'required', 'name' => 'required'];
protected $fillable = ['name'];
/**
* Get users with a certain role
*/
public function userroles()
{
return $this->belongsToMany('User', 'users_roles');
}
}
HomeController authentication function
public function authenticate(){
$rules = array(
'email' => 'required|email',
'password' => 'required|alphaNum|min:3'
);
$validator = Validator::make(Input::all(), $rules);
if ($validator->fails()) {
return Redirect::to('login')
->withErrors($validator)
->withInput(Input::except('password'));
} else {
$userdata = array(
'email' => Input::get('email'),
'password' => Input::get('password')
);
if (Auth::attempt($userdata, true)) {
return Redirect::action('HomeController#index');
} else {
return Redirect::action('HomeController#login')->withInput();
}
}
}
USING THE DATABASE DRIVER
- auth:attempt() and auth::check are working
$this->beforeFilter('admin', ['only' => ['index']]); //filter in controller
//filter in filters;php
Route::filter('admin', function()
{
if(!Auth::check()) return Redirect::action('HomeController#index');
if(!Auth::user()->hasRole('admin')) return View::make('errors.401');
});
This fails with 'Call to undefined method Illuminate\Auth\GenericUser::hasRole()'
EDIT The database driver return a GenericUser Object, and I need my own User object. Don't know where I can change this.
Workaround:I'd rather not use this, ugly code and filters (or views) should not need to do this
Route::filter('admin', function()
{
if(!Auth::check()) return Redirect::action('HomeController#index');
$user = User::find((Auth::user()->id));
if(!$user->hasRole('admin')){ return View::make('errors.401');}
});
USING THE ELOQUENT DRIVER
auth::attempt() succeeds
auth::check() fails
no error on the filter
The problem is your implementation of getAuthIdentifier(). This method should actually return the primary key of your table and not the username that's used for logging in.
So yours should look like this:
public function getAuthIdentifier(){
return $this->id;
}
Or actually, I recommend you clean up your model a bit more since all of the getSomeAuthStuff methods are implemented in the two traits.
Use the default model on github as a base and add all your custom code (roles methods, rules etc)
Background info
The value returned from getAuthIdentifier() will be stored in the session.
When using check() afterwards, retrieveById will be called on the UserProvider. And the EloquentUserProvider does this:
public function retrieveById($identifier)
{
return $this->createModel()->newQuery()->find($identifier);
}
It uses find() which searches for the model by it's primary key (usually id)