So I have Laravel Notifications setup and it's working perfectly fine.
However, I've extend the migration to include an additional id field:
$table->integer('project_id')->unsigned()->nullable()->index();
Thing is, I don't see how I can actually set that project_id field. My notification looks like this:
<?php
namespace App\Notifications\Project;
use App\Models\Project;
use App\Notifications\Notification;
class ReadyNotification extends Notification
{
protected $project;
public function __construct(Project $project)
{
$this->project = $project;
}
public function toArray($notifiable)
{
return [
'project_id' => $this->project->id,
'name' => $this->project->full_name,
'updated_at' => $this->project->updated_at,
'action' => 'project-ready'
];
}
}
So ya, I can store it in the data, but what if I want to clear the notification specifically by "project" instead of by "user" or by "notification".
For instance if they delete the project, I want the notifications for it cleared, but there is no way to access that unless I do some wild card search on the data column.
So is there anyway to insert that project_id in the notification ?
You could create an Observer to update the field automatically.
NotificationObserver.php
namespace App\Observers;
class NotificationObserver
{
public function creating($notification)
{
$notification->project_id = $notification->data['project_id'] ?? 0;
}
}
EventServiceProvider.php
use App\Observers\NotificationObserver;
use Illuminate\Notifications\DatabaseNotification;
class EventServiceProvider extends ServiceProvider
{
public function boot()
{
parent::boot();
DatabaseNotification::observe(NotificationObserver::class);
}
}
And you should be able to access the table using the default model to perform actions.
DatabaseNotification::where('project_id', 11)->delete();
Related
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
});
});
}
}
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.
I have a model named 'Poll'. Inside Poll model I defined a boot method like follows:
public static function boot()
{
parent::boot();
self::created(function($model){
// dd($model);
$speakers = $model->speakers()->get();
// dd($speakers);
// What I want to do here is: create poll options relation from speakers as follows
// $poll->poll_options()->create([
// 'option' => $speaker->name,
// ]);
}
}
I am adding the speakers relation and it is working perfect.
But inside this boot method, inside self::created if I tried to get the speakers relation, it is always empty (dd($speakers) line). Is it because of the boot method runs just after the model is saved into DB and the relations not at all saved?
I am getting newly created model in the line: dd($model) mentioned in the code.
UPDATE
I tried with events also.
My Poll Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
use Cookie;
use App\Events\PollCreated;
class Poll extends Model
{
........
protected $events = [
'created' => PollCreated::class,
];
.......
public function speakers()
{
return $this->belongsToMany('App\Models\Speaker','poll_speaker','poll_id','speaker_id');
}
}
app/Events/PollCreated.php:
namespace App\Events;
use App\Models\Poll;
use Illuminate\Queue\SerializesModels;
class PollCreated
{
use SerializesModels;
public $poll;
/**
* Create a new event instance.
*
* #param Poll $poll
* #return void
*/
public function __construct(Poll $poll)
{
// $this->poll = $poll;
$event = $poll->event()->first();
// dd($event);
// dd($poll->speakers()->get());
// dd($poll->load('speakers'));
}
}
Here also I am not getting speakers, in the line: dd($poll->speakers()->get());
my Speaker model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class Speaker extends Model
{
use CrudTrait;
……..
public function polls()
{
return $this->belongsToMany('App\Models\Poll');
}
……..
}
The problem is with timing as models must always be created before they can be set in a many-to-many relationship. So there is no possible way that in a many-to-many relationship during the created event the relationship is already set as the created events are always raised before the relationships.
Anyone looking for a solution can probably experiment with the chelout/laravel-relationship-events package as this adds relationship events to models.
To be sure, I tested this out with a simple application of users and computers.
User.php
class User extends Model
{
use HasBelongsToManyEvents;
public static function boot() {
parent::boot();
self::created(function($model){
Log::info('user::created');
});
static::belongsToManyAttaching(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Attaching {$relation} {$ids} to user.");
});
static::belongsToManyAttached(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Computers {$ids} have been attached to user.");
});
}
public function computers() {
return $this->belongsToMany(Computer::class, 'user_computers');
}
}
Computer class is the same in reverse. And for the following code:
$user = User::create();
$user->computers()->attach([
Computer::create()->id,
Computer::create()->id
]);
This was the outcome:
user::created
computer::created
computer::created
Attaching computers 69 & 70 to user.
Computers 69 & 70 have been attached to user.
I have to pass the secret author_id of the user when he edit for example an Article and memorize that into the Database in Backpack-Laravel.
How can do that?
I be able to do just this, the value appears in the $request array (I use dd($request) for know that) but isn't stored on the database.
AuthorCrudController.php
public function update(UpdateArticleRequest $request)
{
//dd($request); <-- author_id = Auth::id()
return parent::updateCrud();
}
UpdateArticleRequest.php
public function rules()
{
$this->request->add(['author_id'=> Auth::id()]);
return [
'title' => 'required|min:5|max:255',
'author_id' => 'numeric'
];
}
99 times out of 100, when the value isn't stored it's because that column hasn't been mentioned in your model's $fillable property. Is this it?
Sidenote: Adding the author_id like this works, but if you're using this approach for multiple models, I recommend coding it once for all your models. I use a trait for this. That way, any time an entry is created, the author is saved and you have the have all the methods for getting it in one place, the trait ($this->creator(), this->updator).
My approach to this is this:
1) I have two new columns in my database created_by and updated_by
2) I use a trait like this:
<?php namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Model;
trait CreatedByTrait {
/**
* Stores the user id at each create & update.
*/
public function save(array $options = [])
{
if (\Auth::check())
{
if (!isset($this->created_by) || $this->created_by=='') {
$this->created_by = \Auth::user()->id;
}
$this->updated_by = \Auth::user()->id;
}
parent::save();
}
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
public function creator()
{
return $this->belongsTo('App\User', 'created_by');
}
public function updator()
{
return $this->belongsTo('App\User', 'updated_by');
}
}
3) Whenever I want a model to have this feature, i just need to:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class Car extends Model
{
use CrudTrait;
use CreatedByTrait; // <---- add this line
Hope it helps.
The update function in my backpack setup has $request passed in updateCrud function. The one that you have mentioned does not have the request passed into the parent function.
public function update(UpdateRequest $request)
{
// your additional operations before save here
$redirect_location = parent::updateCrud($request);
return $redirect_location;
}
Using a REST approach I want to be able to save more than one model in a single action.
class MyController extends ActiveController {
public $modelClass = 'models\MyModel';
}
class MyModel extends ActiveRecord {
...
}
That automagically creates actions for a REST api. The problem is that I want to save more than one model, using only that code in a POST will result in a new record just for MyModel. What if I need to save AnotherModel?
Thanks for any suggestion.
ActiveController implements a common set of basic actions for supporting RESTful access to ActiveRecord. For more advanced use you will need to override them or just merge to them your own custom actions where you will be implementing your own code & logic.
Check in your app the /vendor/yiisoft/yii2/rest/ folder to see how ActiveController is structured and what is doing each of its actions.
Now to start by overriding an ActiveController's action by a custom one, you can do it within your controller. Here is a first example where i'm overriding the createAction:
1-
class MyController extends ActiveController
{
public $modelClass = 'models\MyModel';
public function actions()
{
$actions = parent::actions();
unset($actions['create']);
return $actions;
}
public function actionCreate(){
// your code
}
}
2-
Or you can follow the ActiveController's structure which you can see in /vendor/yiisoft/yii2/rest/ActiveController.php by placing your custom actions in separate files. Here is an example where I'm overriding the updateAction by a custom one where i'm initializing its parameters from myController class :
class MyController extends ActiveController
{
public $modelClass = 'models\MyModel';
public function actions() {
$actions = parent::actions();
$custom_actions = [
'update' => [
'class' => 'app\controllers\actions\WhateverAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->updateScenario,
'params' => \Yii::$app->request->bodyParams,
],
];
return array_merge($actions, $custom_actions);
}
}
Now let's say as example that in my new action file app\controllers\actions\WhateverAction.php I'm expecting the Post Request (which i'm storing in $params) to have a subModels attribute storing a list of child models to which I'm going to apply some extra code like relating them with their parent model if they already exists in first place :
namespace app\controllers\actions;
use Yii;
use yii\base\Model;
use yii\db\ActiveRecord;
use yii\web\ServerErrorHttpException;
use yii\rest\Action;
use app\models\YourSubModel;
class WhateverAction extends Action
{
public $scenario = Model::SCENARIO_DEFAULT;
public $params;
public function run($id)
{
$model = $this->findModel($id);
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $model);
}
$model->scenario = $this->scenario;
$model->load($this->params, '');
foreach ($this->params["subModels"] as $subModel) {
/**
* your code related to each of your model's posted child
* for example those lines will relate each child model
* to the parent model by saving that to database as their
* relationship has been defined in their respective models (many_to_many or one_to_many)
*
**/
$subModel = YourSubModel::findOne($subModel['id']);
if (!$subModel) throw new ServerErrorHttpException('Failed to update due to unknown related objects.');
$subModel->link('myParentModelName', $model);
//...
}
// ...
return $model;
}
}
So if I understand you wish to add a new database entry not only for the model you are querying, but for another model.
The best place to do this would be in the AfterSave() or BeforeSave() functions of the first model class. Which one would depend on the data you are saving.