this is my scenario:
I'm using Laravel 5.5.x.
I have two models, linked in one to many way.
class Artwork extends Model
{
//It has timestamps
protected $table = 'artworks';
protected $fillable = [
'artwork_category_id'
];
public function artworkCategory()
{
return $this->belongsTo('App\Models\ArtworkCategory');
}
}
class ArtworkCategory extends Model
{
use SoftDeletes;
protected $touches = ["artworks"];
/**
* #var string
*/
protected $table = 'artwork_categories';
protected $fillable = [
'category_name',
'acronym',
'deleted_at'
];
public function artworks()
{
return $this->hasMany('App\Models\Artwork');
}
}
Touch works correctly, so when I update an artwork category the related artworks updated_at field is updated.
But I need to listen the "touch" event on each artwork.
I've tried inserting "updated" listener on boot method in AppServiceProvider, but it is not fired.
Artwork::updated(function ($model){
\Log::debug("HERE I AM");
});
I've tried using an observer, but no luck.
class ArtworkObserver
{
public function updated(Artwork $artwork)
{
dd($artwork);
}
}
Boot method in AppServiceProvider:
Artwork::observe(ArtworkObserver::class)
Question:
Could somebody show me the right way to do it? Or tell me where am I wrong?
I was not good enough to find an example that helps me how to do it.
Update
I need to achieve this because I have to "fire" Scout to save updated data on Elasticsearch on Artwork index.
Most probably $touches uses mass update, and if you check Events section of Eloquent you'll find following:
When issuing a mass update via Eloquent, the saved and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.
The best that I can think of is that, you update Artworks manually (instead of $touches) when a ArtworkCategory is updated:
class ArtworkCategory extends Model
{
use SoftDeletes;
protected $fillable = [
'category_name',
'acronym',
'deleted_at'
];
public function artworks()
{
return $this->hasMany('App\Models\Artwork');
}
public static function boot()
{
parent::boot();
static::updated(function($artworkCategory)
{
$artworkCategory->artworks->each(function($artwork) {
$artwork->setUpdatedAt($artwork->freshTimestamp());
$artwork->save(); /// Will trigger updated on the artwork model
});
});
}
}
Related
So I have 2 models in this example that is Gig and GigImage. Here's the Gig model
class Gig extends Model
{
use HasFactory, Uuids, SoftDeletes;
protected $fillable = [...]
protected $with = ['gigImages'];
public function gigImages()
{
return $this->hasMany(GigImage::class);
}
}
as you can see $with is referencing the GigImage and so far things worked as expected.
The problem comes when i did the same thing for GigImage
class GigImage extends Model
{
use HasFactory;
protected $fillable = ['image', 'gig_id', 'is_thumbnail'];
protected $with = ['gig'];
public function gig()
{
return $this->belongsTo(Gig::class);
}
}
After I assign the GigImage instance to Gig and tried to execute Gig::first() on tinker it just freezes for a minute and stops working entirely by itself, there's no error message. This occurs when I tried to mention anything related to those two models with any eloquent method. Maybe there's some sort of endless loop going on but I'm not sure what's wrong.
I have a destroy function which allows me to detach models (polymorphic relationship).
public function destroy {
$vaccine = HealthItem::findOrFail($vaccine_id);
$vaccine->detachCategories();
$events = $vaccine->events()->get();
foreach ($events as $event) {
$event->detachCategories();
};
$vaccine->events()->delete();
$vaccine->delete();
}
Here, I detach an "event" model with "detachCategories" (a helper to help me detach my categories)
I collect them and I do a foreach. It works, it is well detached from my table categorizable.
BUT I don't think it's great, right?
I'm going to have to do it for all of my events, every time a model is linked to it and it'll do a lot. So, I tried to make it an event but without success.
My Event Model :
protected static function boot()
{
parent::boot();
static::deleting(function ($event) {
$event->categories()->detach();
});
}
I delete the event with the following line $vaccine->events()->delete();
How would you do it?
Laravel Events should get you there, and if its a big job and you have setup queue workers on your server, you should make sure the event listeners are queued.
The following solution should work:
You likely want to make a contract and a trait for this polymorphic relationship so that you can do better type hinting, and DRY up your code. You can put these in whatever folders make most sense for your project. Say:
interface CategorizableContract
{
// Relationship to access the models categories
}
trait HasCategories
{
// Implement methods above
/**
* Initialize the trait
*
* #return void
*/
protected function bootHasCategories()
{
static::deleting(function($categorizable) {
event(new DestroyCategorizable($categorizable));
});
}
}
Then in your models that have categories:
class Vaccine extends Model implements CategorizableContract
{
use HasCategories;
...
}
Now we can make an event in App\Events:
class DestroyCategorizable
{
use SerializesModels;
/** #var CategorizableContract */
public $categorizable;
/**
* Create an event for deleting categorizable models
*
* #param CategorizableContract $categorizable
*
* #return void
*/
public function __construct(CategorizableContract $categorizable)
{
$this->categorizable = $categorizable;
}
}
Now you can make an event listener, in App\Listeners, like so:
class DetachCategories implements ShouldQueue
{
public function handle(DestroyCategorizable $event)
{
$categorizable = $event->categorizable;
$class = new ReflectionClass($categorizable)
// Detach the categories
DB::table('categorizable')
->where('categorizable_type', $class->getName())
->where('categorizable_id', $categorizable->id)
->delete();
}
}
Now just register your listener in your EventServiceProvider, and away you go!
protected $listen = [
DestroyCategorizable::class => [
DetachCategories::class,
],
];
Now when you call delete on a model with categories, it will automatically detach them for you.
$vaccine->delete();
Even though the model is deleted, it was serialized in the event, so its id and class type will be available to detach the categories in the listener. Hope that helps :)
OK so my User models uses webpatser/laravel-uuid. All migrations are using UUID.
So now my model looks like:
<?php
namespace App\Models;
use App\Models\Traits\Uuid;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use Notifiable;
use Uuid;
public $incrementing = false;
public $timestamps = true;
protected $guarded = [
'uuid',
];
protected $keyType = 'string';
protected $primaryKey = 'uuid';
protected $table = 'users';
protected $dates = [
'created_at',
'updated_at',
];
protected $hidden = [
'password',
'remember_token',
];
public function setPasswordAttribute($password): void
{
$this->attributes['password'] = Hash::make($password);
}
}
I want to use database session driver. I created session table via php artisan session:table. All migrations are done. I obviously had to rename existing user_id column. I've changed it to user_uuid. I know it's not enough as I can't find the logic responsible for populating this db table. I guess it's somewhere in the vendor (Illuminate).
Where is the logic to populate my non-default session column?
Now each open the page gives:
So I know what's the issue, what's causing it, how to change it, but I don't know where to start. Thanks for any hints.
I think you would benefit of a custom session handler because the name of the column user_id is hardcoded into the addUserInformation() method.
Extend the existing DatabaseSessionHandler.php and replace the addUserInformation() method so it looks for the correct column name:
class DatabaseUuidSessionHandler extends DatabaseSessionHandler
{
protected function addUserInformation(&$payload)
{
if ($this->container->bound(Guard::class)) {
$payload['user_uuid'] = $this->userId();
}
return $this;
}
}
Register it in one of your service providers:
class SessionServiceProvider extends ServiceProvider
{
public function boot()
{
Session::extend('databaseUuid', function ($app) {
return new DatabaseUuidSessionHandler;
});
}
}
Finally update SESSION_DRIVER in your .env to use the newly created databaseUuid driver.
Remember that this is untested code and should only be used as a guideline of how this could work.
I am trying to save a field from the created model event but for some reason the stripe_coupon_id column is never being saved. The created event does run as I've tested by trying a dd inside it and it does fire the event but just does not save that column.
class DiscountRate extends Model
{
public $table = "discount_rates";
public $primaryKey = "id";
public $timestamps = true;
public $fillable = [
'id',
'name',
'rate',
'active',
'stripe_coupon_id'
];
public static function boot()
{
parent::boot();
self::created(function ($discountRate) {
$coupon_id = str_slug($discountRate->name);
$discountRate->stripe_coupon_id = $coupon_id;
});
}
}
In my controller I simply call a service function which calls the default Laravel model create function:
public function store(DiscountRateCreateRequest $request)
{
$result = $this->service->create($request->except('_token'));
if ($result) {
return redirect(route('discount_rates.edit', ['id' => $result->id]))->with('message', 'Successfully created');
}
}
discount_rates table:
The created event is triggered after your model is created. In this case, you need to to call $discountRate->save() in the end in order to update the model which you just created.
As alternative, you can use creating event instead. In this case you don't need to call save() in the end because the model is not yet saved in your database.
A big difference in creating event is that the model does not have an id yet if you use auto-incrementing which is default behavior.
More info about events you can find here.
you have to set stripe_coupon_id before creating. So replace static::creating instead of self::created in boot method of DiscountRate model.
What I'm looking for is something like this
public function store(Help $help)
{
$help->save();
return response
}
I have added the model class is Routes.php file like this
Route::model('help', 'App\Help');
Route::resource('api/help', 'HelpController');
And this is my Help.php file
class Help extends Model
{
use SoftDeletes;
protected $primaryKey = 'id';
protected $table = 'help';
protected $dates = ['deleted_at'];
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = array('name','description','deleted_at','created_at', 'updated_at');
}
Binding is happening to an extent, i.e, there is a new row in the table but the attributes "name" and "description" are empty.
I think you misunderstood the route model binding concept...
I think, what you really want, is something like that:
public function store(Illuminate\Http\Request $request)
{
$help = new Help($request->all());
$help->save();
return back()->with('success', true);
}
Route Model Binding is more likely to be used for example on the update method, as you have an existing instance of your model, you can work with.
For example:
public function update(Illuminate\Http\Request $request , Help $help) {
// help exists and the correct model instance is automatically resolved through route model binding...
$help->fill( $request->all() );
$help->save();
return redirect()->back()->with( 'updated', true );
}
You can run the command
php artisan route:list
To check where Route Model Binding can be used. The variables you see in {}
like {help}