Recently I'm having problems with using traits, I created some before and it seemed to work, but now I created another one to take action on a creating event, here it is
trait Contributes
{
public static function bootContributes()
{
static::creating(function ($model) {
if (!$model->getKey())
{
Contribution::create([
'contributing_id' => $model->id,
'contributing_type' => $model->class_table,
]);
}
});
}
}
and I included it in multiple models like use Contributes; I think there is something I don't know about it.
I tried dd($model); inside the trait, it works outside of the event but not inside the event
Instead of the above method, I figured this one is a better practice and works a lot more better with laravel:
public static function boot()
{
self::created(
function($model)
{
Contribution::create([
'contributing_id' => $model->id,
'contributing_type' => $model->table
]);
}
);
}
Related
Using multiple boot traits with the same event will fire just the first one and ignore the rest bootable traits.
class Tag extends Model
{
use HasKey; // this will work (but if i put it bellow it will not work)
use HasSlug; // this's not (but if i put it above it will work)
}
trait HasKey
{
public static function bootHasKey()
{
static::creating(
fn (Model $model) => $model->key = 'value'
);
}
trait HasSlug
{
public static function bootHasSlug()
{
static::creating(
fn (Model $model) => $model->slug = 'value'
);
}
}
I found that it's not supported at the moment, maybe in the future releases of Laravel!
https://github.com/laravel/framework/issues/40645#issuecomment-1022969116
I can't figure out if Laravel is failing to boot my model trait, or is simply not seeing the user as being authed within the trait.
All of this code works perfectly fine when I test my app manually, but when I run unit tests I am getting a bunch of errors.
Here is the trait I've added to App\User and a few other models:
trait HasCompany
{
public static function bootHasCompany()
{
if (auth()->check()) {
static::creating(function ($model) {
$model->company_id = auth()->user()->company_id;
});
static::addGlobalScope('company_id', function (Builder $builder) {
$builder->where('company_id', auth()->user()->company_id);
});
}
}
public function company()
{
return $this->belongsTo('App\Company');
}
}
The purpose of this trait is to automatically add the logged in users company_id to any models they create, and restrict their access only to models they have created. I should mention that all App\User's have a company_id set in the database.
So as I've said, when I attempt to create a model when logged into my app everything works great. The trait works perfectly. However, unit tests don't seem to care for this trait. Here is an example test that does not work:
class RoleTest extends TestCase
{
use WithFaker;
public $user;
public function setup()
{
parent::setUp();
$this->user = App\User::create([
'company_id' => $faker->randomNumber(),
'name' => $this->faker->firstName,
'email' => $this->faker->email,
'password' => $this->faker->password,
]);
}
public function tearDown()
{
parent::tearDown();
$this->user->delete();
}
public function testAdd()
{
$response = $this->actingAs($this->user)->json('POST', 'roles/add', [
'_token' => csrf_token(),
'name' => $this->faker->word,
]);
$response->assertStatus(200)->assertJson(['flash' => true]);
}
}
I'm getting a 500 response instead of a 200 response because the model should automatically be obtaining the company_id from $this->user, but it is not. This is only happening for unit tests.
Here is the model code:
class Role extends Model
{
use HasCompany;
protected $fillable = ['company_id', 'name'];
}
Why aren't the unit tests booting the trait properly? It seems like actingAs doesn't work for authorization within traits, or is failing to boot it's traits entirely.
In your unit tests, the user model is first booted when the user is created by the setup function. At that time, no user is authenticated (as actingAs follows later). So, the auth()->check() only happens once when the user is created.
I think instead of checking if authenticated once (during boot), you should check if authenticated during the user creation.
Remove if(auth()->check()) from bootHasCompany and add it inside the Eloquent event closures like so:
static::creating(function ($model) {
if(auth()->check())
{
$model->company_id = auth()->user()->company_id;
}
});
static::addGlobalScope('company_id', function (Builder $builder) {
if(auth()->check())
{
$builder->where('company_id', auth()->user()->company_id);
}
});
I used this package called revisionable so I tried to add it to my package.json and run the migration and everything worked fine.
But when I try to create a record and then update them it doesn't fill out the revisions table?
I am using "venturecraft/revisionable": "^1.28", and Laravel 5.5
Here is my code in my model
So here is what I have done in my model
use Venturecraft\Revisionable\Revisionable;
use Venturecraft\Revisionable\RevisionableTrait;
class AnalysisRequest extends Revisionable
{
use SoftDeletes;
use RevisionableTrait;
protected $revisionEnabled = true;
protected $revisionCleanup = true;
protected $historyLimit = 100; //Stop tracking revisions after 500 changes have been made.
protected $dontKeepRevisionOf = array(
'client_id', 'service_id', 'categories_id', 'methodologies_id', 'current_version', 'due_date'
);
protected $keepRevisionOf = array(
'sample_description',
'special_instruction',
'status',
'rushable'
);
When did I did wrong?
Can someone shed some light for me.
Thanks in advance.
For those who uses this package out there. There is no problem with the new versions of Laravel.
The problem is by using the eloquent methods of laravel especially with the update method.
So instead of updating a model like this example below.
$analysis_request = AnalysisRequest::where('id', $input['id'])->update([
'client_id' => $input['client_id'],
'sample_description' => $input['sample_description'],
'special_instruction' => $input['special_instruction'],
'rushable' => $input['rushable'],
'status' => 'for_testing'
]);
You have to do it this way in order to make revisions of the model. See below
$analysis_request = AnalysisRequest::where('id', $input['id'])->first();
$analysis_request->client_id = $input['client_id'];
$analysis_request->sample_description = $input['sample_description'];
$analysis_request->special_instruction = $input['special_instruction'];
$analysis_request->status = 'for_testing';
$analysis_request->save();
As you can see I used the first() method to fetch my model and update it using save() You can't use the save() method if you don't use the first().
Reference link of the issue in github.
I know it's hard to do it like that way. But for now you will be forced to if you want to make your life easier when creating revisions instead of manually doing it on your own. But hey it still depends on your case.
I hope the author makes a fix on this soon in the next release.
Okay, I did some digging on this, and it looks like it's a limitation of the package (it doesn't include the updating event - ref: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasEvents.php).
You can try to "override" the trait function and add the event yourself though:
class AnalysisRequest extends Model {
use RevisionableTrait {
bootRevisionableTrait as protected unused;
}
....
//Override trait function
public static function bootRevisionableTrait() {
static::saving(function ($model) {
$model->preSave();
});
static::saved(function ($model) {
$model->postSave();
});
static::created(function($model){
$model->postCreate();
});
static::deleted(function ($model) {
$model->preSave();
$model->postDelete();
});
//Add in the update events
static::updating(function ($model) {
$model->preSave();
});
static::updated(function ($model) {
$model->postSave();
});
}
}
I am trying to listen to model events using laravel observers .The problem is when i submit my form (update or creating new records), nothing happened at all .Do i miss something ?
app.php
'providers' => [
...
App\Providers\CasesManagerServiceProvider::class,
]
CasesManagerServiceProvider.php
class CasesManagerServiceProvider extends ServiceProvider
{
public function boot( )
{
Cases::observe(CasesObserver::class);
}
public function register()
{
}
}
CasesObserver.php
class CasesObserver
{
private $cases;
public function __construct(Cases $cases){
$this->cases = $cases;
}
public function creating(Cases $case)
{
dd('creating');
}
public function saved(Cases $case)
{
dd('saved');
}
public function updating($case)
{
dd('updating');
}
public function updated($case)
{
dd('updated');
}
}
Cases.php
class Cases extends Model
{
const UPDATED_AT = 'modified_at';
protected $dispatchesEvents = [
'updating' => CasesObserver::class,
'updated' => CasesObserver::class,
'creating' => CasesObserver::class,
'saved' => CasesObserver::class,
];
}
for me, the problem was registering observer in the register() method!
so when I put it in the boot() method every thing worked well! the reason is the order of running methods in service providers which are mentioned hear
hope be useful
Ok i have found my answer . All the problem was when I added
use app\Observers\CasesObserver; in CasesManagerServiceProvider.php instead of use App\Observers\CasesObserver; .
Yes the Camel case of App was the problem, so i changed to App and all things are working fine now.
It seems to be a misuse of Composer and Laravel themselves.
You should inform them that you have added some files and configurations:
To autoload the files:
composer dump
To reconfigure the cache:
php artisan config:cache
Hope this help you too!
You do not need to use $dispatchesEvents in your case. You should try to remove $dispatchesEvents from model, and remove __constructor() from CasesObserver.
The reason is that you have to add a HasEvents trait to your model
<?php
use Illuminate\Database\Eloquent\Concerns\HasEvents;
class MyModel extends Model
{
use HasEvents;
//your code goes here
}
Not possible according to the documentation. When issuing a mass update or delete query via Eloquent.
I'm trying to create a trait (for models) that would automatically write all changes made to the model in 'adjustments' table. It would save changes in before and after jsons.
This is the code so far (from Laracasts):
trait LoggingTrait
{
public static function boot()
{
parent::boot();
static::updating(function($action){
$action->adjustments()->create(
[
'user_id' => Auth::id(),
'before' => json_encode(array_intersect_key($action->getOriginal(), $action->getDirty())),
'after' => json_encode($action->getDirty())
]);
});
}
public function adjustments()
{
return $this->morphMany(Adjustment::class, 'adjustable');
}
}
This is working very well, except it doesn't save changes to related models.
To make it more clear, this is my Action model:
class Action extends Model
{
use LoggingTrait;
public function actionTypes()
{
return $this->belongsToMany(ActionType::class);
}
public function users()
{
return $this->belongsToMany(User::class);
}
public function spreadingMaterial()
{
return $this->belongsTo(SpreadingMaterial::class);
}
}
The trait logs all the changes made to the actual Action model, but doesn't care for the changes made to the $action->users(), $action->spreadingMaterials() and $action->actionTypes(). How would I get these changes within the static::updating(...) event?
Or if that is not possible, any other idea on how to tackle this problem is more than welcome.