laravel model - how to dynamically set a column value in the Model? - laravel

When a User is created, e.g. with the following line in a controller:
$user = $this->create($request->all());
This will insert a user record with the form values (name, email, password etc).
But if we want to set a "hidden" user fields/colums on the model and DB table, e.g. a special unique generated token (user.token), We dont want to do this in every controller.
If laravel had a service layer, it could be done here, but better would be to do it in the model itself.
e.g. by catching a beforeSave callback, and having some code generate the token and set the corresponding field before it gets written to the DB. I see that the model has saved() event/observers, but this looks like it happens after the save, and I dont want to put this logic in an external class, It belongs in the model, and the documenation doesnt say if the observer can modify the model (by setting columns in this case)
Any suggestions?

It is possible to define event listeners directly within your model. E.g. add a boot method to your User model:
/**
* Define model event callbacks.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::saving(function ($model) {
$model->token = str_random(60);
});
}
Alternative, more verbose implementation:
/**
* Define model event callbacks.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::saving(function ($model) {
if (method_exists($model, 'beforeSave')) $model->beforeSave();
});
}
/**
* Before save event listener.
*
* #return void
*/
public function beforeSave()
{
$this->token = str_random(60);
}

Related

Laravel 8 - Insert in related table in model

Whenever I create a "user", I have to create a line in different tables (like account).
I know that in the controller I can create the user and account like this:
$user = User::create($user_inputs);
$account = $user->account()->create($account_inputs);
$OtherTables...
Is there a way to do this in the model? Always when someone creates a user from another controller, will the lines be automatically inserted in the other tables. Or is it always necessary to indicate it in the controller every time?
You can use Laravel observer
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Handle the user "created" event.
*
* #param \App\User $user
* #return void
*/
public function creating(User $user)
{
$user->account()->create([
// your data
]);
}
}
You can use model events for this. https://laravel.com/docs/9.x/eloquent#events-using-closures
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
// This code will be called every time a new user is inserted into the system
static::created(function ($user) {
$user->account()->create([ 'name' => $user->name ])
});
}
}
There are few more events you can use within booted method, the name tells clearly what they do.
creating
created
updating
updated
saving
saved
deleting
deleted

Loses Auth :: user () when declaring an Oberserver in AppServiceProvider

I'm trying to use a local scope in one of my models but for this I need to check the user permission, so I try to get the autenticated user by Auth::user().
But it givens me a null because I have an Observer declared for this model, and if I comment the declaration of the Observer the Auth::user() method give me a user authenticated.
There is a correct way or place to declare the Observer and in the model I can get the authenticated user, because I need to use Observers and get in boot method the authenticated user?
Laravel Framework 6.5.2
AppServiceProvider Don't work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Conciliador::observe(ConciliadorObserver::class);
Proposta::observe(PropostaObserver::class);
}
AppServiceProvider work auth in model
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//Conciliador::observe(ConciliadorObserver::class);
//Proposta::observe(PropostaObserver::class);
}
Model does not have user logged in when Observer is declared in AppServiceProvider
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
$user = Auth::user();
dd($user); // null if Observer is declared in AppServiceProvider
if($user && $user->tipo == 'admin-gerenciador'){
$conciliadores = $user->conciliadores->pluck('id')->toArray();
static::addGlobalScope('Conciliadores', function (Builder $builder) {
$builder->whereIn('id',$conciliadores);
});
}
}
Don't call Auth::user() anywhere. It may trigger authentication side-effects. It is highly recommended to do that only in controllers and middleware.
But you can safely call Auth::user() if you check by Auth::hasUser() in advance; it checks if the user is ALREADY authenticated.
So your code goes like:
/**
* The "booting" method of the model.
*/
protected static function boot(): void
{
static::addGlobalScope('Conciliadores', function (Builder $query) {
if (Auth::hasUser() && Auth::user()->tipo === 'admin-gerenciador') {
$query->whereKey(Auth::user()->conciliadores->modelKeys());
}
});
}
It's very simple solution. Even middleware is unnecessary.
EDIT
This scope is always available, but actually apply conditions only if the user is already authenticated.
You shouldn't be doing this in your model's boot method like that. The boot method is only called once for the model, not for every model instance. The first time the model is used boot gets called, which would be when you are adding the observer for it in the Service Provider in your case; which would be way before the request is dispatched to a route and through the middleware stack. (There is no session at this point, so no authenticated user.)
You probably want to add your global scope to your model via a middleware.

Eloquent how to do something when delete or update operate on a special model

Most of my db table contain create_user_id and update_user_id
How can l update this two field automatic when l use save(), update(), insert(), createOrUpdate() and etc method.
For example, l execute this script:
$model = Model::find(1);
$model->model_f = 'update';
$model->save();
then this record's model_f updated, and update_user_id updated, too.
l know eloquent can manage update_time automatic and l have use it already. But l want to do something else when update or insert or delete
PS: l have a constant named USERID to remember current user's id
You could make use of Observers.
You can hook to the following events on your Model:
retrieved
creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored
Let me give you an example where we are trying to hook into the events emitted by the App/User model. You can change this to match your particular Model later on.
To create an observer, run the following command:
php artisan make:observer UserObserver --model=User
Then you can hook to specific events in your observer.
<?php
namespace App\Observers;
use App\User;
class UserObserver
{
/**
* Handle the User "saved" event.
*
* #param \App\User $user
* #return void
*/
public function saved(User $user)
{
//
}
/**
* Handle the User "created" event.
*
* #param \App\User $user
* #return void
*/
public function created(User $user)
{
//
}
/**
* Handle the User "updated" event.
*
* #param \App\User $user
* #return void
*/
public function updated(User $user)
{
//
}
}
Since, in your particular case, you want to hook into these 3 events, you can define the events above and perform additional operations to your model when those events are called.
Don't forget to register this observer in your AppServiceProvider.
<?php
namespace App\Providers;
use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
User::observe(UserObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
There is pretty simple way to automatically update the create_user_id and update_user_id
Step1:
Open you app folder and create the new file named as UserStampsTrait.php
Step:2
and paste the following code
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
trait UserStampsTrait
{
public static function boot()
{
parent::boot();
// first we tell the model what to do on a creating event
static::creating(function($modelName='')
{
$createdByColumnName = 'create_user_id ';
$modelName->$createdByColumnName = Auth::id();
});
// // then we tell the model what to do on an updating event
static::updating(function($modelName='')
{
$updatedByColumnName = 'update_user_id';
$modelName->$updatedByColumnName = Auth::id();
});
}
}
Thats it
Step:3
Open you model which needs to updated the corresponding models automatically
for Example it may be Post
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\UserStampsTrait;
class Post extends Model
{
use UserStampsTrait;
}
Thats it

Middleware before Model injection

I have a design doubt I would like to share.
I have a model in Laravel with an Observer at retrieved:
class MailingObserver
{
public function retrieved($mailing)
{
// we retrieve HTML content from disk file
$mailing->setAttribute('content', \Illuminate\Support\Facades\Storage::disk('mailings')->get("{$mailing->id}-{$mailing->slug}.html"));
$mailing->syncOriginal();
}
}
which retrieve an attribute stored in a plain text instead of database.
The site is a multibrand platform so disk('mailings') is different per each logged user. This configuration is loaded in a middleware according to the the current logged user.
Up to here all is fine.
Now the "problem". I have a Controller which injects the entity Mailing:
class MailingCrudController extends CrudController
{
/**
* Sends the mailing
* #param Request $request
* #param \App\Mailing $mailing
*/
public function send(Request $request, \App\Mailing $mailing)
{
// WHATEVER
}
}
When the model is injected the retrieved Observer method is fired but the Middleware wasn't still executed so mailings disk is still not set up.
I don't know how to change this order: first execute middleare, then the model injection.
One approach
I tried in AppServiceProvider to add:
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$middleware = new \App\Http\Middleware\CheckBrandHost();
$middleware->setBrandInformation(request());
$middleware->loadBrandConfig(request()->get('brand.code_name'));
}
Would you approve this solution? What problems can cause it to me? Is it the proper way to do it?
Thanks all!

How to set hidden attributes to an Controller method in Laravel5.0?

I want to hide some data which selected from database but reinitialize from some method in Controller not defined in its Models.
function ddd(){
return Client::select($this->_client)->with([
'Contact'=>function($s){
//$this->setHidden('use_id');
//$s->setHidden('use_id');
$s->select($this->_contact);
},
'Employer'=>function($s){$s->select($this->_employers);},
])->get();
}
You requirement is not very clear. However. I am assuming that you have 3 models Client hasOne Contact and belongsTo Employer.
In order to hide the use_id property of the Client model you can define a hidden property in your model
class Client extends Model
{
//will hide the `use_id` from the model's array and json representation.
protected $hidden = ['use_id'];
//Relations
/**
* Get the Contact which the given Client has.
* #return \Illuminate\Database\Eloquent\Relations\HasOne
* will return a App\Contact model
*/
public function contact()
{
return $this->hasOne('App\Contact');
}
/**
* Get the Employee to which the given Client belongs.
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
* will return a App\Employer model
*/
public function employer()
{
return $this->belongsTo('App\Employer');
}
}
Then in probably your ClientsController in some action ddd
public function ddd()
{
return Client::with('contact')->with('employer')->get();
}

Resources