So, I have a custom Model extension class called RecursiveModel:
use Illuminate\Database\Eloquent\Model;
use ... RecursiveHelper;
class RecursiveModel extends Model {
private $recursiveHelper = null;
public function __construct(){
$this->recursiveHelper = new RecursiveHelper();
parent::__construct();
}
public function save(array $options = []){
parent::save($options);
}
...
// Additional methods available for Recursive Models (self-referenced `parent_id` relationships)
}
And, a Model that extends this RecursiveModel class instead of the base Model class:
use ... RecursiveModel;
use Illuminate\Database\Eloquent\SoftDeletes;
class Line extends RecursiveModel {
use SoftDeletes;
protected $table = "lines";
protected $primaryKey = "id";
public function parent(){
return $this->belongsTo(self::class, "parent_id", "id");
}
public function children(){
return $this->hasMany(self::class, "parent_id", "id");
}
}
All is well and good, and with previously imported records (back when Line extended Model and not RecursiveModel, I was able to use my RecursiveHelper methods/logic without issue. Now, I'm trying to refresh my database, which calls a Seeder:
use Illuminate\Database\Seeder;
use ... Slugger;
use ... Line;
class LinesSeeder extends Seeder {
public function run(){
$parentLine = Line::create([
"name" => "Line Item",
"slug" => $this->slugger->slugify("Line Item"),
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s"),
]);
$childLine = Line::create([
"name" => "Child Line Item",
"slug" => $this->slugger->slugify("Child Line Item"),
"parent_id" => $parentLine->id,
"created_at" => date("Y-m-d H:i:s"),
"updated_at" => date("Y-m-d H:i:s"),
]);
...
}
}
As previously stated, when Line extended Model and not RecursiveModel, this code worked without issue. But now, I'm running into this error:
SQLSTATE[HY000]: General error: 1364 Field 'name' doesn't have a default value (SQL: insert into lines
(updated_at, created_at) values (2018-08-13 15:56:45, 2018-08-13 15:56:45))
The Line::create([...]); doesn't seem to be receiving the parameter passed; is there something I'm missing when extending Model.php? I've tried adding:
public function create(array $options = []){
parent::create($options);
}
To RecursiveModel, but that just throws another error (and I don't think the create() method is a part of Model.php, but rather Builder.php.)
Also, it's not an issue with protected $fillable, nor is it an issue with setting 'strict' => true, on my mysql connection; already tried both of those to no avail.
As suggested, updated __construct method of RecursiveModel to:
public function __construct(array $attributes = []){
$this->recursiveHelper = new RecursiveHelper();
return parent::__construct($attributes);
}
Unfortunately, still getting the same error.
Edit: Line.php had a __construct method that was carried over from when I was applying $this->recursiveHelper model by model; solution was to update signature to match (as noted above) or remove __construct from extending models.
Model constructors need to take in an array of attributes:
public function __construct(array $attributes = [])
Related
I have :
A many_to_many relationship between a Group Model and a User Model;
A Group_User Pivot class (I need it to observe its events);
An additional field named status in my group_user table;
An observer that observes Group_User model.
The setup is as follows
class Group extends Model
{
/* Other stuff */
protected $table = 'groups';
public function users()
{
return $this->belongsToMany('App\User')
->using(GroupUser::class)
->withPivot(['status', 'is_confirmed'])
->withTimestamps();
}
/* Other stuff */
}
And
class GroupUser extends Pivot
{
protected $table = 'group_user';
}
In a controller, I have a $group->users()->attach($user); (hence creating a new GroupUser object and dispatching the created event).
In my observer, I have the following piece of code:
public function saved(GroupUser $groupUser)
{
// Here, I wanted to access $groupUser->status that leads to an error
dd($groupUser);
}
I expected my $groupUser to have the status attribute as, not only is it in the database, I also specified it in my users() function (just to be sure).
EDIT: here, I meant that when I dd($groupUser->status); it returns null
How can I access this field ?
Some remarks :
I already used in my code $user->pivot->status and it works as expected !
I tried few solutions like explicitly adding status to $attributes or $fillable protected variables of my GroupUser class but that had no effect.
When I dd my $group_user, I get:
//...
#attributes: array:4 [
"user_id" => 1
"group_id" => 1
"created_at" => "2020-10-12 21:07:53"
"updated_at" => "2020-10-12 21:07:53"
]
#original: array:4 [
"user_id" => 1
"group_id" => 1
"created_at" => "2020-10-12 21:07:53"
"updated_at" => "2020-10-12 21:07:53"
]
//...
Finally, I thought of a hack but I don't quite like it as it seems like a lot of work for such a simple task. I wanted to maybe, add a getStatusAttribute function to my model that would return $this->PivotParent->users()->/*try to make some condition to find the correct user*/->pivot->status;
Any ideas ?
EDIT 2: when I pass the extra argument to attach it works but I want to use the database default value for the field
EDIT 3: I found this thread and it made me think that what I want to do is impossible :( I think my attach used to work correctly because (and on only because) my SGBD would set the default value for a field that wasn't specified !
I created your example in my test environment and I had no problem with the Pivot class not returning the data on the saved observer. Perhaps you have to set the guarded at your pivot class?
protected $guarded = []; //accepts all fields to be filled
Here are my files
Group.php
class Group extends Model
{
protected $guarded = [];
public function users()
{
return $this
->belongsToMany(User::class)
->using(GroupUser::class)
->withPivot(['status', 'is_verified'])
->withTimestamps();
}
}
GroupUser.php
class GroupUser extends Pivot
{
protected $guarded = [];
public static function boot()
{
parent::boot();
static::saved(function (GroupUser $groupUser) {
dd($groupUser->toArray());
});
}
}
And my Unit Test
class GroupTest extends TestCase
{
use RefreshDatabase;
/** #test */
public function it_belongs_to_many_users()
{
$this->withoutExceptionHandling();
$user = factory(User::class)->create();
$group = factory(Group::class)->create();
$group
->users()
->attach($user, ['is_verified' => 1, 'status' => 1]);
//data will be dumped here by GroupUser::class saved() observer
}
}
here is My Test Output
array:6 [
"user_id" => 1
"group_id" => 1
"created_at" => "2020-10-12T20:56:20.000000Z"
"updated_at" => "2020-10-12T20:56:20.000000Z"
"is_verified" => 1
"status" => 1
]
I hope that might help you.
OK I found an answer. It's a bit annoying but it works !
WARNING: there is an important remark at the end (edit)
I needed to use withPivotValue (link to the doc, original pull and original proposal) instead of withPivot change my Group->users() so my code now is
class Group extends Model
{
/* Other stuff */
protected $table = 'groups';
public function users()
{
return $this->belongsToMany('App\User')
->using(GroupUser::class)
->withPivotValue(['status' => MY_DEFAULT_VALUE, 'is_confirmed' => MY_OTHER_DEFAULT_VALUE])
->withTimestamps();
}
/* Other stuff */
}
Now that's weird as the doc says :
Set a where clause for a pivot table column.
In addition, new pivot records will receive this value.
What's annoying me is that I have to define the default value in two different places, the migration AND the Model. It would be nice if I had a way to synchronize them.
EDIT 1 : DON'T USE withPivotValue in an existing function or it will break your code ! now my $group->users() returns only users with the status set to the default value so i had to define it in another function called newUsers and keep users as it was
class Group extends Model
{
public function users()
{
return $this->belongsToMany('App\User')
->using(GroupUser::class)
->withPivot(['status', 'is_confirmed'])
->withTimestamps();
}
public function newUsers()
{
return $this->users()
->withPivotValue('status', DEFAULT_STATUS);
}
}
I tried all the options. Still, it is showing "Constant expression contains invalid operations". I am using Laravel 5.5, Please Help. I need to define table name in constant and use it in Model.
I wrote in Model:
protected $table = Config::get('constants.dbTable.EMAILTEMPLATE');
And In constant.php inside Config:
return [ 'langs' =>
[
'es' => 'www.domain.es',
'en' => 'www.domain.us' // etc
],
'siteTitle' => 'HD Site',
'pagination' => 5,
'tagLine' => 'Do the best',
'dbTable'=>[
'EMAILTEMPLATE' => 'stmd_emailTemplate'
]
];
I want to use emailTemplate table.
Based on the code you have posted in the comment, you are trying to assign a value into a property in your model but you are assigning it too early (assumed from the keyword protected.) You can't do this:
class SomeModel extends Model
{
protected $someProperty = config('some.value'); // Too early!
}
because you are trying to initialize a property that requires a run-time interpretation.
There's a workaround; use your constructor.
class SomeModel extends Model
{
protected $someProperty; // Define only...
public function __construct() {
parent::__construct(); // Don't forget this, you'll never know what's being done in the constructor of the parent class you extended
$this->someProperty = config('some.value');
}
}
I am using model factories in NewsTableSeeder, but I get this error when I entered db:seed.
I want to know why I can't use create() in my seeder.
Here is my News model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class News extends Model
{
protected $table = 'news';
protected $primaryKey = 'id';
public function home_news_lists() {
return $this->select('id', 'news_title', 'news_update')
->orderBy('news_update', 'DESC')
->limit(5)
->get();
}
public function lists() {
return News::all();
}
}
Model Factories:
$factory->define(App\Models\News::class, function (Faker\Generator $faker)
{
static $password;
$faker = $faker->create('zh_TW');
return [
'news_title' => $faker->sentence(),
'news_content' => $faker->paragraph(),
'news_author' => $faker->name(),
'news_pageviews' => $faker->numberBetween(1, 100),
'news_file' => ' ',
'news_img' => $faker->imageUrl($width, $height, 'business'),
'created_at' => $faker->dateTimeBetween('2012', 'now', 'zh_TW'),
'updated_at' => $faker->dateTimeBetween('2015', 'now', 'zh_TW')
];
});
NewsTableSeeder :
<?php
use Illuminate\Database\Seeder;
class NewsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\Models\News::class, 50)->create();
}
}
I can't tell 100% without seeing exactly the error you got, but I do believe there is no create() method on the $faker object.
I believe what you mean to do is:
$factory->define(App\Models\News::class, function (Faker\Generator $faker)
{
static $password;
$faker = \Faker\Factory::create('zh_TW'); // change to this
return [
...
];
}
I would just create a new faker generator (\Faker\Generator) that gets returned from calling \Faker\Factory::create($locale) and use that instead. Otherwise, I believe your next best option is to override wherever Laravel instantiates the \Faker\Generator $faker object that gets passed into the callback, but that may get hacky if Laravel doesn't provide a clean way to do it.
The create() method is a static call on the \Faker\Factory method. It accepts a locale as the parameter and uses en_US as the default locale.
$faker = $faker->create('zh_TW');
The error message said this code is wrong.
What is your purpose to use this code?
I'm overriding the create() Eloquent method, but when I try to call it I get Cannot make static method Illuminate\\Database\\Eloquent\\Model::create() non static in class MyModel.
I call the create() method like this:
$f = new MyModel();
$f->create([
'post_type_id' => 1,
'to_user_id' => Input::get('toUser'),
'from_user_id' => 10,
'message' => Input::get('message')
]);
And in the MyModel class I have this:
public function create($data) {
if (!Namespace\Auth::isAuthed())
throw new Exception("You can not create a post as a guest.");
parent::create($data);
}
Why doesn't this work? What should I change to make it work?
As the error says: The method Illuminate\Database\Eloquent\Model::create() is static and cannot be overridden as non-static.
So implement it as
class MyModel extends Model
{
public static function create($data)
{
// ....
}
}
and call it by MyModel::create([...]);
You may also rethink if the auth-check-logic is really part of the Model or better moving it to the Controller or Routing part.
UPDATE
This approach does not work from version 5.4.* onwards, instead follow this answer.
public static function create(array $attributes = [])
{
$model = static::query()->create($attributes);
// ...
return $model;
}
Probably because you are overriding it and in the parent class it is defined as static.
Try adding the word static in your function definition:
public static function create($data)
{
if (!Namespace\Auth::isAuthed())
throw new Exception("You can not create a post as a guest.");
return parent::create($data);
}
Of course you will also need to invoke it in a static manner:
$f = MyModel::create([
'post_type_id' => 1,
'to_user_id' => Input::get('toUser'),
'from_user_id' => 10,
'message' => Input::get('message')
]);
If I try declaring a property, like this:
public $quantity = 9;
...it doesn't work, because it is not considered an "attribute", but merely a property of the model class. Not only this, but also I am blocking access to the actually real and existent "quantity" attribute.
What should I do, then?
An update to this...
#j-bruni submitted a proposal and Laravel 4.0.x is now supporting using the following:
protected $attributes = array(
'subject' => 'A Post'
);
Which will automatically set your attribute subject to A Post when you construct. You do not need to use the custom constructor he has mentioned in his answer.
However, if you do end up using the constructor like he has (which I needed to do in order to use Carbon::now()) be careful that $this->setRawAttributes() will override whatever you have set using the $attributes array above. For example:
protected $attributes = array(
'subject' => 'A Post'
);
public function __construct(array $attributes = array())
{
$this->setRawAttributes(array(
'end_date' => Carbon::now()->addDays(10)
), true);
parent::__construct($attributes);
}
// Values after calling `new ModelName`
$model->subject; // null
$model->end_date; // Carbon date object
// To fix, be sure to `array_merge` previous values
public function __construct(array $attributes = array())
{
$this->setRawAttributes(array_merge($this->attributes, array(
'end_date' => Carbon::now()->addDays(10)
)), true);
parent::__construct($attributes);
}
See the Github thread for more info.
This is what I'm doing now:
protected $defaults = array(
'quantity' => 9,
);
public function __construct(array $attributes = array())
{
$this->setRawAttributes($this->defaults, true);
parent::__construct($attributes);
}
I will suggest this as a PR so we don't need to declare this constructor at every Model, and can easily apply by simply declaring the $defaults array in our models...
UPDATE:
As pointed by cmfolio, the actual ANSWER is quite simple:
Just override the $attributes property! Like this:
protected $attributes = array(
'quantity' => 9,
);
The issue was discussed here.
I know this is really old, but I just had this issue and was able to resolve this using this site.
Add this code to your model
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->user_id = auth()->id();
});
}
Update/Disclaimer
This code works, but it will override the regular Eloquent Model creating Event
I use this for Laravel 8 (static and to dynamically change attributes)
<?php
namespace App\Models\Api;
use Illuminate\Database\Eloquent\Model;
class Message extends Model
{
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
protected static function defAttr($messages, $attribute){
if(isset($messages[$attribute])){
return $messages[$attribute];
}
$attributes = [
"password" => "123",
"created_at" => gmdate("Y-m-d H:i:s"),
];
return $attributes[$attribute];
}
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::creating(function ($messages) {
$messages->password = self::defAttr($messages, "password");
$messages->created_at = self::defAttr($messages, "created_at");
});
}
}
Set the attribute value in the constructor:
public function __construct()
{
$this->attributes['locale'] = App::currentLocale();
}