I want to run a similar task on some different eloquent events in laravel.
For example Suppose I have a Question model. For my purpose, I used boot() function on the model like this :
class Question extends Model
public static function boot ()
{
parent::boot();
static::updated(function ($Question) {
//some Tasks on $Question
});
static::updating(function ($Question) {
//some Tasks on $Question
});
static::created(function ($Question) {
//some Tasks on $Question
});
static::creating(function ($Question) {
//some Tasks on $Question
});
}
}
As you see in all events , similar Tasks run and may have large code.
what is best and short approach to do that?
You have several ways to refactor this code:
Run saved() and saving() instead of the whole four events. saved() will substitute both created() and updated(), while saving() will substitute both creating() and updating().
Fire a the same job inside all of them:
public static function boot ()
{
Question::saved(function ($question) {
dispatch(new \App\Jobs\job($question));
});
}
Add the code to protected function inside the class (this is the least recommended)
By the way, Model events should be placed in the boot method of a Service Provider, not the Model. Model has no boot method.
Related
I'm building online courses website in laravel. I want to display the teachers info in public pages and I don't want the id to be revealed in public requests. should I use UUID or encryption ?
for example :
teacher/1/courses
teacher/1/info
I think uuid is the best approach, it's fairly readable, it is not cryptographical safe but seriously unpractical to break.
Pretty easy to implement using model binding. Imagine a route with this controller.
class CourseController {
public function index(Teacher $teacher) {
...
}
}
Route::get('teacher/{teacher}/courses', 'CourseController#index');
You need a uuid column on the model, do a migration for that. Else overwrite how model binding is resolved on the Teacher.php model.
class Teacher {
public function getRouteKey(): string
{
return $this->uuid;
}
public function resolveRouteBinding($value, $field = null)
{
return self::where('uuid', $value)->firstOrFail();
}
}
From here you just need to set uuid when you create your teacher model, hook into the creating event and it should be fine.
class Teacher {
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->uuid = resolve(UuidFactory::class)->uuid4();
});
}
I need to dynamically create different relationships (oneToOne, manyToMany, oneToMany) for my model with different tables and foreign keys and then retrieve my model with all this relations. Is there any way to do so? For example, instead of doing something like this:
public function relOne()
{
return $this->hasMany('one', 'foreign_one', 'one');
}
public function relTwo()
{
return $this->hasMany('two', 'foreign_two', 'two');
}
I need to do something like this:
$model->createRelation('relOne', function ($model) {
return $model->hasMany('one', 'foreign_one', 'one');
});
$model->createRelation('relTwo', function ($model) {
return $model->hasMany('two', 'foreign_two', 'two');
});
P.S. I have Laravel 6.X
i recommend using
eloquent-dynamic-relation
you can install it:
composer require i-rocky/eloquent-dynamic-relation
in your model use the trait:
use Rocky\Eloquent\HasDynamicRelation;
class MyModel extends Model {
use HasDynamicRelation;
}
then you can simply write:
MyModel::addDynamicRelation('some_relation', function (MyModel $myModel) {
return $myModel->hasMany(SomeRelatedModel::class);
});
I have a model in laravel and I want to do something after the first time which an object of my model is created. the simplest way is to add a static boot method inside my model's class like the code below:
class modelName extends Model
{
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
});
}
}
so far so good! the problem is: the ONLY parameter that created method accepts is the model object itself(according to the documentation) :
Each of these methods receives the model as their only argument.
https://laravel.com/docs/5.5/eloquent#events
I need more arguments to work with after model creation. how can I do that?
Or is there any other way to do something while it's guaranteed that the model has been created?
laravel version is 5.5.
You're close. What I would probably do would be to dispatch an event right after you actually create the model in your controller. Something like this.
class WhateverController
{
public function create()
{
$model = Whatever::create($request->all());
$anotherModel = Another::findOrFail($request->another_id);
if (!$model) {
// The model was not created.
return response()->json(null, 500);
}
event(new WhateverEvent($model, $anotherModel));
}
}
I solved the issue using static property in eloquent model class:
class modelName extends Model
{
public static $extraArguments;
public function __construct(array $attributes = [],$data = [])
{
parent::__construct($attributes);
self::$extraArguments = $data ;
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
self::$extraArguments; // is available in here
});
}
}
It works! but I don't know if it may cause any other misbehavior in the application.
Using laravel events is also a better and cleaner way to do that in SOME cases.but the problem with event solution is you can't know if the model has been created for sure and it's time to call the event or it's still in creating status ( and not created status).
I have model events:
protected static function boot()
{
parent::boot();
static::creating(function ($questionnaire) {
// Same code here
});
static::updating(function ($questionnaire) {
// Same code here
});
}
Is there a way of combining creating and updating together or is it better to put the same code in some sort of partial to reuse in each event?
https://laravel.com/docs/5.6/eloquent#events
When a new model is saved for the first time, the creating and created events will fire. If a model already existed in the database and the save method is called, the updating / updated events will fire. However, in both cases, the saving / saved events will fire.
The saving event is fired when a model is being created or being updated.
The saved method can be used to handle both the created and updated events:
protected static function boot()
{
parent::boot();
static::saved(function ($questionnaire) {
// Code here
});
}
However, in the cases, when more than these two events should run the same code, you can use closures:
protected static function boot()
{
parent::boot();
$closure = function ($questionnaire) {
// Code here
};
static::created($closure);
static::updated($closure);
static::deleted($closure);
}
You can go about it like so...
// store the events you want to capture
protected static $updateOnEvents = ['saved','deleted',...];
protected static function booted()
{
// loop through them and apply the logic
foreach (static::$updateOnEvents as $event) {
static::$event(function($questionnaire){
// your code here
});
}
}
Note that the booted method is available from Laravel 7.x, if you are using a lower version, you can use booting...
When I read the Laravel 5.3's source code, I find the following codes
public function withGlobalScope($identifier, $scope)
{
$this->scopes[$identifier] = $scope;
if (method_exists($scope, 'extend')) {
$scope->extend($this);
}
return $this;
}
I am confused about why it is the following codes in the method, is it used anywhere?
if (method_exists($scope, 'extend')) {
$scope->extend($this);
}
Thanks!
It's meant for extending the Builder within the context of a Scope class.
Inside a global Scope (class which extends the Scope interface), you can create an extend function next to the apply function.
This extend function is called with the Eloquent builder as parameter. Imagine some PopularUsersScope, which only gets very popular users:
public function apply(Builder $builder, Model $model): void
{
$builder->join(...)->where(...) // determine popularity
}
public function extend(Builder $builder)
{
$builder->macro('demote', function (Builder $builder) {
return $builder->update(...) // query to make user not so popular
});
}
Now lets have a model Clan, which is a group of users, some of which are popular. We have a constrained relation on Clan:
public function popular_users(): belongsToMany
{
return $this->hasMany(User::class)
->withGlobalScope('popularUsers', new PopularUsersScope)
}
Because we defined a macro within the scope extend function, we can do $user->demote() for any popular user, but not for normal users.
This might be a bit of a silly example, but it can be useful in fairly abstract use cases.
See SoftDeletingScope in the Laravel Framework code for a practical example: SoftDeletable items need some extra methods on the Builder for restoring and eager loading deleted items.
Extend in SoftDeletingScope dynamically adds multiple extensions to the Builder, from an Array $this->extensions, and registers an replacement for the default delete function on the model:
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
$builder->onDelete(function (Builder $builder) {
$column = $this->getDeletedAtColumn($builder);
return $builder->update([
$column => $builder->getModel()->freshTimestampString(),
]);
});
}