laravel nova observer trying to get property on non object - laravel

I set a database connection by the auth()->user()-dbname
This works as desired using this in the model
public function __construct() {
$this->connection = auth()->user()->dbname;
}
Now I want to observe the model on creation, update, etc.
I tried to use
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$itemIds = $model->item_ids;
... update another model based on the $itemIds
});
But Nova is not recognizing the static::creating function
So I created an Observer (I think a better choice) however when the observer is called it does not recognize the
auth()->user()->dbname property
Why doesn't the observer recognize auth?

This may be caused because there is no authenticated user. Try dumping and see what it throws you.
public function __construct() {
// Should throw an User model OR null.
dd(auth()->user());
// Alternatively, you could use the Logger
\Log::info(json_encode(auth()->user()));
$this->connection = auth()->user()->dbname;
}
If auth()->user() is null, then no user is logged in and as you might have guessed, null is a non-object.

Thanks for the suggestions but none would work for me. I gave up on Observers in Nova. I used the boot() function. This is how I setup milti tenant.
In the _constructor I added this
public function __construct() {
parent::__construct(); // needed before boot would fire
$this->connection = auth()->user()->dbname;
}
Then my boot() function became the observer
protected static function boot()
{
parent::boot();
static::creating(function($item) {
$item->event_id = Event::currentEventID();
});
}

Related

Why is the Eloquent model "booted" method not firing on Laravel 7.x?

I have this simple code on an Eloquent model to fire during the model creating event to auto-create a slug for the model.
use Illuminate\Support\Str;
...
protected static function booted()
{
static::creating(function (self $model) {
if (!$model->slug) {
$model->slug = Str::slug($model->title);
}
});
}
It's not firing when it's up on our staging site, and I have literally no idea why. It works fine locally. I'm just getting General error: 1364 Field 'slug' doesn't have a default value... when trying to create a new model.
When I change it to this, it works as expected:
use Illuminate\Support\Str;
...
protected static function boot()
{
parent::boot();
static::creating(function (self $model) {
if (!$model->slug) {
$model->slug = Str::slug($model->title);
}
});
}
However, this is a Laravel 7.x install so surely the booted method should work?
Does anyone know of a reason why booted would not be firing while boot works?

Eloquent model event `updating` isn't firing in laravel 7

I'm trying to catch the model event updating in laravel 7 but it doesn't fire.
This is the place where the model get's changed:
public function update(Request $request, Model $model) {
$model->update($request->input());
return new Resource($model);
}
I also tried this to update the values:
public function update(Request $request, Model $model) {
$model->attribute1 = $request->get('value1');
$model->attribute2 = $request->get('value2');
$model->attribute3 = $request->get('value3');
$model->save();
return new Resource($model);
}
And here I'm trying to catch the event within the bill model:
protected static function boot() {
static::updating(function ($model) {
// code
});
}
What am I doing wrong?
You have to call parent::boot() at the start of your boot method:
protected static function boot() {
parent::boot();
static::updating(function ($model) {
// code
});
}
Laravel 7 added a booted method to make it easier:
Adding booting / booted methods to Model
Currently, users who extend the boot method to add event listeners on
model events must remember to call parent::boot() at the start of
their method (or after). This is often forgotten and causes confusion
for the user. By adding these simple place-holder extension points we
can point users towards these methods instead which do not require
them to call any parent methods at all.
From the docs:
Instead of using custom event classes, you may register Closures that
execute when various model events are fired. Typically, you should
register these Closures in the booted method of your model:
<?php
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::created(function ($user) {
//
});
}
}

How do I use a policy on an index that doesn't use the model the policy belongs to?

What I am trying to do is apply a policy on a control method that lists a bunch of records instead of just one record like most of the examples I have seen.
Instead of checking against the ThoughtRecords I want to check the signed in user hashedId to the user that's being queried hashedId in the controller index() method.
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
AuthServiceProvider.php
protected $policies = [
'App\ThoughtRecord' => 'App\Policies\ThoughtRecordPolicy',
];
public function boot()
{
$this->registerPolicies();
}
ThoughtRecordPolicy.php
public function view(User $signedInUser, User $client)
{
//return true;
dd('Policy working');
//return $signedInUser->id === $client->id;
}
ThoughtRecordController.php
public function index($userHashedId)
{
$client = User::where('hashed_id', $userHashedId)->first();
$this->authorize('view', ThoughtRecord::class, $client);
$records = ThoughtRecord::where('user_id', $client->id)->latest()->paginate(1);
return ThoughtRecordResource::collection($records);
}
Error
Too few arguments to function App\Policies\ThoughtRecordPolicy::view()
I have also tried:
$this->authorize('view', $client);
This action is unauthorized.
As said:
Apparently in the Laravel docs the model class needs to be passed on actions that don't require a model. So I'm confused how to make this work.
You need pass both the ThoughtRecord::class and the $client into an array:
$this->authorize('view', [ThoughtRecord::class, $client]);

Access session data from parent controller w/o passing it in

Can I access session data from Controller, without passing the request from MyController?
class Controller extends BaseController
{
public function __construct()
{
// ** next line throws error:
// "Session store not set on request."
$userdata = request()->session()->get('userdata');
// I want to inject `userdata` into every template without
// passing data from child controllers.
view()->share(['userdata' => $userdata);
}
}
class MyController extends Controller
{
public function __construct(Request $request)
{
// This works, so the data is in fact in the session.
// I don't want to pass it, or `$request` to the parent from here.
$userdata = $request->session()->get('userdata');
...
}
}
The reason it won't be working in your __construct() method is because the StartSession middleware won't have been run yet.
To get around this you can simply use the middleware() method on the controller:
public function __construct()
{
$this->middleware(function ($request, $next) {
$userdata = $request->session()->get('userdata');
view()->share(compact('userdata'));
return $next($request);
});
}
Laravel 5.3 Upgrade guide (Scroll down the Controllers section)
In Laravel 5.3, you can't access the session or authenticated user in your controller's constructor because the middleware has not run yet.
As an alternative, you may define a Closure based middleware directly in your controller's constructor.

How to always use withTrashed() for model Binding

In my app, I use soft delete on a lot of object, but I still want to access them in my app, just showing a special message that this item has been deleted and give the opportunity to restore it.
Currently I have to do this for all my route parametters in my RouteServiceProvider:
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::withTrashed()->find($value);
});
Route::bind('post', function ($value) {
return Post::withTrashed()->find($value);
});
[...]
}
Is there a quicker and better way to add the trashed Object to the model binding ?
Jerodev's answer didn't work for me. The SoftDeletingScope continued to filter out the deleted items. So I just overrode that scope and the SoftDeletes trait:
SoftDeletingWithDeletesScope.php:
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingWithDeletesScope extends SoftDeletingScope
{
public function apply(Builder $builder, Model $model)
{
}
}
SoftDeletesWithDeleted.php:
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\SoftDeletingWithDeletesScope;
trait SoftDeletesWithDeleted
{
use SoftDeletes;
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingWithDeletesScope);
}
}
This effectively just removes the filter while still allowing me to use all the rest of the SoftDeletingScope extensions.
Then in my model I replaced the SoftDeletes trait with my new SoftDeletesWithDeleted trait:
use App\Models\Traits\SoftDeletesWithDeleted;
class MyModel extends Model
{
use SoftDeletesWithDeleted;
For Laravel 5.6 to 7
You can follow this doc https://laravel.com/docs/5.6/scout#soft-deleting. And set the soft_delete option of the config/scout.php configuration file to true.
'soft_delete' => true,
For Laravel 8+
You can follow this doc https://laravel.com/docs/8.x/routing#implicit-soft-deleted-models. And append ->withTrashed() to the route that should accept trashed models:
Ex:
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
You can add a Global Scope to the models that have to be visible even when trashed.
For example:
class WithTrashedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->withTrashed();
}
}
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WithTrashedScope);
}
}
Update:
If you don't want to show the deleted objects you can still manually add ->whereNull('deleted_at') to your query.

Resources