Laravel model inheritance - php

I am using the Toddish/Verify library for Laravel as it includes 99% of what I need for my project. All I need is to add some fields.
I have added them in a migration, and I want to add them also to mass creation:
use Toddish\Verify\Models\User as VerifyUser;
class User extends VerifyUser
{
public function __construct () {
array_merge ($this->fillable, array(
'salutation', 'title', 'firstname', 'lastname', 'phonenumber', 'mobilenumber'
));
}
}
However, when I run my creation test:
public function testUserCreation () {
$user = User::create(
[
'username' => 'testusername',
'email' => 'email#test.com',
'password' => 'testpassword',
'salutation' => 'MrTest',
'title' => 'MScTest',
'firstname' => 'Testfirstname',
'lastname' => 'Testlastname',
'phonenumber' => 'testPhoneNumber',
'mobilenumber' => 'testMobileNumber',
]
);
$this->assertEquals($user->salutation, 'MrTest');
$this->assertEquals($user->title, 'MScTest');
$this->assertEquals($user->firstname, 'Testfirstname');
$this->assertEquals($user->lastname, 'Testlastname');
$this->assertEquals($user->phonenumber, 'testPhoneNumber');
$this->assertEquals($user->mobilenumber, 'testMobileNumber');
}
I get this:
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 users.username may not be NULL (SQL: insert into "users" ("updated_at", "created_at") values (2014-03-03 09:57:41, 2014-03-03 09:57:41))
in all tests that involve user creation, as if it had forgotten about the parents attributes when saving the model.
What am I doing wrong?

The problem is that you're overriding what I assume is the Eloquent constructor, so the values are never getting passed.
Change __construct to look like the following.
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
array_merge ($this->fillable, array(
'salutation', 'title', 'firstname', 'lastname', 'phonenumber', 'mobilenumber'
));
}
The Model::create method will actually create a new instance of the model and pass the array into the __construct. You're overriding this and preventing it from passing the information through.
Note If you decide to override core methods like you've done here, always check inheritance and make sure you aren't breaking anything.

Related

Laravel Feature Testing: Factory Records Exist but Test Says Otherwise

I'm having a strange problem with my Laravel feature tests.
I have a createTeam method that creates a factory record and persists it in memory-database.
public function createTeam(): static
{
$this->team = Team::factory()->create([
'name' => $this->user->name . ' Team',
]);
$this->team->assignMember($this->user, TeamRoleTypes::OWNER);
return $this;
}
Then I go on and try to test an action.
public function testUserCanDeleteItsOwnTeam()
{
$this->createTeam();
$this
->useToken()
->deleteJson(route('team.delete', ['team' => $this->team->id]), [
'name' => 'Second team',
])
->assertOk();
}
However, the response says "No query results for model [App\Models\Team] e99a7514-58e2-4d29-91f2-f0c3a034a419". When I check the same model for existence in the same test by using something like Team::find("e99a7514-58e2-4d29-91f2-f0c3a034a419") and it says it's there!
Does anyone have any idea why such thing happens?
Turns out, the problem lied with the factory and the mystical behavior of Laravel/PHPUnit/Memory DB or whatever...
What happened was caused by the autogenerated factory class trying to fill the timestamp fields manually as can be seen below:
class TeamFactory extends Factory
{
protected $model = Team::class;
public function definition(): array
{
return [
'id' => Str::uuid(),
'name' => $this->faker->name(),
'deleted_at' => Carbon::now(),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];
}
}
The last three definitions were causing the cancer and now everything is working as expected after deletion of these little buggers.

Laravel exists custom validation rule unable to validate user id with phpunit

I have a validation rule taken from the Laravel Documentation which checks if the given ID belongs to the (Auth) user, however the test is failing as when I dump the session I can see the validation fails for the exists, I get the custom message I set.
I have dumped and died the factory in the test and the given factory does belong to the user so it should validate, but it isn't.
Controller Store Method
$ensureAuthOwnsAuthorId = Rule::exists('authors')->where(function ($query) {
return $query->where('user_id', Auth::id());
});
$request->validate([
'author_id' => ['required', $ensureAuthOwnsAuthorId],
],
[
'author_id.exists' => trans('The author you have selected does not belong to you.'),
]);
PHPUnit Test
/**
* #test
*/
function adding_a_valid_poem()
{
// $this->withoutExceptionHandling();
$user = User::factory()->create();
$response = $this->actingAs($user)->post(route('poems.store'), [
'title' => 'Title',
'author_id' => Author::factory()->create(['name' => 'Author', 'user_id' => $user->id])->id,
'poem' => 'Content',
'published_at' => null,
]);
tap(Poem::first(), function ($poem) use ($response, $user)
{
$response->assertStatus(302);
$response->assertRedirect(route('poems.show', $poem));
$this->assertTrue($poem->user->is($user));
$poem->publish();
$this->assertTrue($poem->isPublished());
$this->assertEquals('Title', $poem->title);
$this->assertEquals('Author', $poem->author->name);
$this->assertEquals('Content', $poem->poem);
});
}
Any assistance would be most appreciated, I'm scratching my head at this. My only guess is that the rule itself is wrong somehow. All values are added to the database so the models are fine.
Thank you so much!
In your Rule::exists(), you need to specify column otherwise laravel takes the field name as column name
Rule::exists('authors', 'id')
Since column was not specified, your code was basically doing
Rule::exists('authors', 'author_id')

Laravel - Trying to get property "id" of non-object while using POST Method

Am writing an endpoint with Laravel using using. When I tested on postman using POST Method, I got this error:
ErrorException: Trying to get property 'id' of non-object in file C:\xampp\htdocs\testing-file\testing\app\Http\Controllers\ApiController.php on line 912
Controller
public function storeBilling(Request $request)
{
// $billing = Billing::create($request->all());
// return response()->json(['success' => $billing], $this-> successStatus);
$validator = Validator::make($request->all(), [
'network' => 'required'
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
// Creating a record in a different way
$createBilling = Billing::create([
'user_id' => $request->user()->id,
'network' => $request->network,
'sender' => $request->sender,
'recipient' => $request->recipient,
'message' => $request->message,
'amount' => $request->amount,
'billing_type' => $request->billing_type,
]);
return new BillingResource($createBilling);
}
Model
class Billing extends Model
{
protected $table = 'billing';
protected $fillable = [
'network' ,
'sender',
'recipient',
'message',
'timestamp',
'created_at',
'updated_at',
'amount',
'billing_type',
'user_id',
'service_name',
'package',
'email',
'user_id'
];
public function user() {
return $this->belongsTo('App\User');
}
}
Resource
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Billing;
class BillingResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'network' => $this->network,
'sender' => $this->sender,
'recipient' => $this->recipient,
'message' => $this->message,
'amount' => $this->amount,
'billing_type' => $this->billing_type,
'email' => $this->email,
'user' => $this->user,
'service' => $this->service,
'package' => $this->package,
// Casting objects to string, to avoid receive create_at and update_at as object
'timestamp' => (string) $this->timestamp,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at
];
}
}
If I use this POST Method:
http://localhost/testing-file/stesting/api/storeBilling?network=100
It suppose to post into the database, but I got this error:
ErrorException: Trying to get property 'id' of non-object in file C:\xampp\htdocs\testing-file\testing\app\Http\Controllers\ApiController.php on line 912
'user_id' => $request->user()->id
Your error is saying that $request->user() is not an object, so you cannot access its parameters using object notation, e.g. ->id.
If you dd($request->user) you may see that you are not getting what you thought you were getting - it may be an array, or it may not be the right value at all.
If it is an array, you can access the value like $request['user']['id']. It really depends what you are passing in your POST request.
$request->user()->id is incorrect.
If you want the current user you can use Auth::user().
In the beginning of your question you said you are trying to build an endpoint using Lravel ..
Postman will not have access to the user object unless authenticated, if authenticated then this should work ::
$request->user()->id or Auth::user()->id or $request["user"]["id"]
on you
public function storeBilling(Request $request)
You write $createBilling = Billing::create([
'user_id' => $request->user()->id, and this create error.
Or is preferable to have $createBilling = Billing::create([
'user_id' => Auth::user()->id, to find the id of the user authentificate.
don't forget to add use Auth; at the beginning of the controller
Going through a same Hassle it's happening because relationship finding its relation with billing table but it did not find so giving this error please check your database have related entry's and try again and make sure you have right relationship with table.

Laravel Eloquent: How to store model values before saving?

I have a custom function within one of my models. It looks like this:
public function newWithTeam($data, $team_id = false){
$levels = Permissions::get_levels();
$this->email = $data['email'];
$this->password = bcrypt($data['password']);
$this->username = $data['username'];
$this->save();
$profile = new Profile(['name' => $data['name'],'bio' => $data['bio']]);
$this->profile()->save($profile);
}
Here, you can see I store the email, password and username as object properties, before hitting save()
Instead, I'd like to do this in one line, something like:
$this->store(['email' => $data['email], 'password' => $data['password], 'username' => $data['username']]);
$this->save();
I am aware that the create() method exists, but when I use this, the following line
$this->profile()->save($profile); does not work properly. I think the create() function does not work the same as save() for some reason! Is there any equivalent to the store() function as above?
You can use the fill() method to achieve what you are looking for.
But before using it, you should know a few things.
Laravel models are protected against mass-assignment by security reasons, to use the fill() method you will need to define what properties of your model can be filled using the fillable or the guarded properties.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserModel extends Model
{
protected $fillable = ['email', 'password', 'username'];
public function newWithTeam($data, $team_id = false){
$levels = Permissions::get_levels();
$this->fill([
'email' => $data['email'],
'password' => bcrypt($data['password']),
'username' => $data['username']
]);
$this->save();
$profile = new Profile([
'name' => $data['name'],
'bio' => $data['bio']
]);
$this->profile()->save($profile);
}
}
The fillable property functions like a white-list for mass-assignment. If you want the other way, you can use the guarded property that will function like a black-list. Which means that every column listed within the guarded property will not be available for mass-assignment and everything else will, it's your choice.
About your last statement, if you look at the implementation of the create() method you will find that it accepts a regular php array, while the save() method will accept an Eloquent Model instance. That's why create() will not work receiving your $profile variable.
I hope this helps.
The method that You're looking for is fill:
$this->fill([
'email' => $data['email'],
'password' => $data['password'],
'username' => $data['username']]
);
$this->save();
You can use it for both creating and updating.
You can use fill directly:
$this->fill($data);
$this->save();
It will use the array keys as object attributes, so make sure you'll use the same.
Also, make sure you have set the $fillable in your model (http://laravel.com/docs/eloquent#mass-assignment):
class x extends Eloquent {
protected $fillable = ['email','password','username'];
...
}
I'm not 100 % sure I understood you correctly, but it sounds like you want to make a oneliner update without screwing up the profile creation. In that case, you can simply use the update() method:
$this->update(['email' => $data['email], 'password' => bcrypt($data['password]), 'username' => $data['username']]);
That saves the new values for you.
So what happens when you run $this->create()? Are you getting an error?
Consider that create() will return the new object that you created so you could try:
$user = $this->create([
'email' => $data['email],
'password' => $data['password],
'username' => $data['username']
]);
$user->profile()->create([
'name' => $data['name'],
'bio' => $data['bio']
]);
Didn't test this so see how you get on and leave a comment if you have any further issues. Happy coding!

Customizing model-stored validation rules in Laravel

Let's say, I have a User model in Laravel like this:
class User extends Eloquent implements UserInterface, RemindableInterface {
public static $rules = array(
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
'password_confirmation' => 'required|min:8'
);
...
}
Rules, stored in model, will be reused both for login and register forms but problem occurs when there's no need for password confirmation (e.g. login form). And there could be many such situations where the rules should be changed.
So, is there any pure method how to modify model-stored validation rules for different cases in Laravel?
Do I have to reorganize my rule storage approach at all?
Thanks!
You can add rules dynamically when you need them.
For example:
If I am right, you only need the password_confirmation rule when registering a user and when updating a password. So, in your model, do not add the password_confirmation rule .
public static $rules = array(
'email' => 'required|email',
'password' => 'required|min:8|confirmed'
}
How to add the rule dynamically:
To register a user, the password_confirmation field is required. So, from your controller, you can always add rules like the following:
$rules = User::$rules;
$rules['password_confirmation'] = 'required|min:8';
and sometimes you may need to add rules based on user input.
For example:
If a user selects Australia as country, they must also select a state.
$v = Validator::make($data, $rules ));
$v->sometimes('state', 'required', function($input)
{
return $input->country == 'Australia';
});
Late to the game but per Laravel docs you can use a 'sometimes' rule.
http://laravel.com/docs/validation
In brief:
In some situations, you may wish to run validation checks against a field only if that field is present in the input array. To quickly accomplish this, add the sometimes rule to your rule list:
'password_confirmation' => 'sometimes|required|min:8|confirmed'
I do something like this.
In the Model:
public static $rules = [
'create' => [
'first_name' => 'min:3',
'last_name' => 'min:3',
'email' => 'required|email|unique:users',
'password' => 'required|min:5|confirmed'
],
'edit' => [
'first_name' => 'other',
'last_name' => 'other',
'email' => 'other',
'password' => 'other|min:5'
]
];
In the Controller:
$validator = Validator::make( $input, User::$rules['edit'] ); # Or User::$rules['create']
if ( $validator->fails() ) { code }
As far as i can see the Ardent Package handles model validation well for Laravel > 5.0. It appears to supoort all of the built-in validation features (such as the use of 'sometimes' when you only want to validate a field if it's passed) as well as extending them.
http://packalyst.com/packages/package/laravelbook/ardent

Categories