lumen observer is not working on the eloquent model - laravel

I am using lumen 5.5. I am trying to make the observer called on while updating/deleting the model. When i tried that with user model, observer is not calling. When i did that with the events everything works fine. It's not even shows any errors.
Here is my code:
AppServiceProvider.php
....
use App\Models\User;
use App\Observers\UserObserver;
...
public function boot() {
User::observe(UserObserver::class);
}
App\Models\User.php
...
public static function changeCustomerStatus(int $customerID): int{
$customer = self::where([
'id' => $customerID,
'user_type' => app('config')->get('user_type.CUSTOMER')
])
->first();
if ($customer) {
$customer->status = $customer->status == app('config')->get('status.ACTIVE') ? app('config')->get('status.DEACTIVE') : app('config')->get('status.ACTIVE');
if ($customer->save()) {
return $customer->status;
}
return 0;
}
else
return 0;
}
...
App\Observers\UserObserver.php
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver {
public function updated(User $user) {
if ($user->status === app('config')->get('status.DEACTIVE')) {
app('log')->info('updated');
}
}
public function saved(User $user) {
if ($user->status === app('config')->get('status.DEACTIVE')) {
app('log')->info('saved');
}
}
public function deleted(User $user) {
app('log')->info('deleted');
}
}
I even did the composer dump-autoload. But no luck

Lumen doesn't have observe feature.
You can use Events instead or make custom observer and call its functions from your code.
Read docs here - Events

Lumen does not have model observers the way Laravel does. I agree with using events or implementing your custom observers. If you choose to go with the latter, here is a post that might help.
https://link.medium.com/ZHsJwJuvC5

Related

How to use policy in laravel livewire (return, it is not a trait)?

I have category policy as below partial code.
class CategoryPolicy
{
use HandlesAuthorization;
public function view(User $user, Category $category)
{
return true;
}
}
Then, I call from livewire component inside the mount method.
class Productcategorysetup extends Component
{
use CategoryPolicy;
public function mount()
{
$this->authorize('view',CategoryPolicy::class);
}
}
I got an error message
App\Http\Livewire\Generalsetting\Productcategorysetup cannot use App\Policies\CategoryPolicy - it is not a trait
Any advice or guidance on this would be greatly appreciated, Thanks.
To use authorization in Livewire, you need to import the AuthorizesRequests trait first, and use that in your class.
Secondly, the first argument to authorize() when using view, is the instance of a model - in your case, a category. But this sounds like you want to list categories, i.e. the "index" file - which means you want to check for viewAny (as view is for a specific resource). In that case, the second argument is the class-name of the model, rather than the instance of a model.
<?php
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use App\Models\Category;
class Productcategorysetup extends Component
{
use AuthorizesRequests;
public function mount()
{
$this->authorize('viewAny', Category::class);
}
}
Then in your policy,
class CategoryPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, Category $category)
{
return true;
}
}

Laravel route model binding without global scope

I have following route group in my laravel 8.0 app:
Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
Route::post('/approve/{transaction:uuid}', [OfflineTransactionController::class, 'approve'])
->name('approve');
Route::post('/reject/{transaction:uuid}', [OfflineTransactionController::class, 'reject'])
->name('reject');
});
And Transaction model is:
class Transaction extends Model implements CreditBlocker
{
//....
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AuthUserScope());
}
//....
}
And this is my AuthUserScope:
class AuthUserScope implements Scope
{
private string $fieldName;
public function __construct($fieldName = 'user_id')
{
$this->fieldName = $fieldName;
}
public function apply(Builder $builder, Model $model)
{
$user = Auth::user();
if ($user) {
$builder->where($this->fieldName, $user->id);
}
}
}
Now the problem is when an admin wants to approve or reject a transaction, 404 Not found error will throws. How can I pass this?
Customizing The Resolution Logic
If you wish to define your own model binding resolution logic, you may
use the Route::bind method. The closure you pass to the bind
method will receive the value of the URI segment and should return the
instance of the class that should be injected into the route. Again,
this customization should take place in the boot method of your
application's RouteServiceProvider:
Solution
What you can do is change the parameter name(s) in your routes/web.php file for the specific route(s).
Route::prefix('offline_transaction')->name('offline_transaction.')->group(function () {
Route::post('/approve/{any_transaction}', [OfflineTransactionController::class, 'approve'])
->name('approve');
Route::post('/reject/{any_transaction}', [OfflineTransactionController::class, 'reject'])
->name('reject');
Note the any_transaction. Change that to whatever naming convention you find most convenient.
Then, in your app/Providers/RouteServiceProvider.php file, change your boot(...) method to something like this:
use App\Models\Transaction;
use Illuminate\Support\Facades\Route;
// ...
public function boot()
{
// ...
Route::bind('any_transaction', function($uuid) {
return Transaction::withoutGlobalScopes()->where('uuid', $uuid)->firstOrFail();
});
// ...
}
// ...
Then in your controller app/Http/Controllers/OfflineTransactionController.php file, access the injected model:
use App\Models\Transaction;
// ...
public function approve(Transaction $any_transaction) {
// ...
}
// ...
Credits: Using Route Model Binding without Global Scope #thomaskim
Addendum
If you would like to remove a specific global scope from the route model bound query, you may use
withoutGlobalScope(AuthUserScope::class) in the boot(...) method of the app/Providers/RouteServiceProvider.php file.
Another approach is that I can use Route::currentRouteNamed in AuthUserScope class as following, which I prefer to use instead of Route::bind:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
class AuthUserScope implements Scope
{
private string $fieldName;
public function __construct($fieldName = 'user_id')
{
$this->fieldName = $fieldName;
}
public function apply(Builder $builder, Model $model)
{
$user = Auth::user();
if ($user && !Route::currentRouteNamed('admin.*')) {
$builder->where($this->fieldName, $user->id);
}
}
}

Laravel: immutable Trait

I need that when a document is considered closed, you won't be able to modify, update or delete it anymore.
I was thinking to use a trait like an "ImmutableTrait".
I've done this:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
trait ImmutableTrait
{
protected $isImmutable = false;
public function setAttribute($key, $value)
{
if ($this->isImmutable) {
return $this;
}
return parent::setAttribute($key, $value);
}
}
Then in my model:
use Illuminate\Database\Eloquent\Model;
use App\Traits\ImmutableTrait;
class MedicalRecord extends Model
{
use ImmutableTrait;
public function closeDocument()
{
$this->isImmutable = true;
}
}
Finally the controller:
public function closeDocument(Document $document)
{
.....
$document->closeDocument();
$document->saveorfail();
}
Then, if I try to retrieve the closed model and update a field, I shouldn't be able to do it:
Route::put('{document}/updateStatus', 'DocumentController#updateStatus');
class DocumentController extends Controller
{
....
public function updateStatus(Document $document)
{
$document->status= "TEST";
$document->saveorfail();
}
}
Calling the API with the id of a closed document, should fail the update, but this is not happening. The field is updated normally.
Obviously I'm missing something. But what?
Thank you all!
Just for reference if anyone needs this.
I ended up creating the following trait:
<?php
namespace App\Traits;
use App\Exceptions\ImmutableModelException;
trait ImmutableModelTrait {
public function __set($key, $value)
{
if ($this->isClosed)
{
throw new ImmutableModelException();
}
else {
//do what Laravel normally does
$this->setAttribute($key, $value);
}
}
}
The problem with my first solution, as #mrhn stated in one comment, was that I was searching for the "isImmutable" variable on a new model instance but I wasn't persisting that variable in the DB table.
So now my "Document" table has a field "isClosed" that becomes true when the document is considered closed.

Why Observer's Saved method not working for while update the record in laravel 5.3

I have project in laravel 5.3 and i am using Observer for activity log of user, for that i have created one obeserver with saved() and deleted() method.
The saved() method is working fine for new record, while update the record saved() is not getting call nor updated() method.
I also try with deleted() method, that is also not getting call, here below is my code, please help.
public function __construct()
{
// echo "dsd"; die();
}
public function saved($user)
{
if ($user->wasRecentlyCreated == true) {
// Data was just created
$action = 'created';
} else {
// Data was updated
$action = 'updated';
}
UserAction::create([
'user_id' => Auth::user()->id,
'action' => $action,
'action_model' => $user->getTable(),
'action_id' => $user->id
]);
}
public function deleting($user)
{
dd($user);
}
}
public static function boot() {
parent::boot();
parent::observe(new \App\Observers\UserObserver);
}
Everything seems ok, so i guess something bigger is at fault here. Normally the best practice for registering observers is to do it in a provider class boot() method.
public function boot()
{
User::observe(UserObserver::class);
}
EDIT
For model events to trigger you have to use the model and not update the data through a query.
$discount = Discounts::find($request->edit_id);
$discount->fill($data);
$discount->save();

Passing a variable to a master template in laravel

In laravel i have defined a route like this
Route::get('/', array(){
'as' => 'index',
'uses' => 'HomeController#index'
});
The function index() in the HomeController contains
public function index(){
$index = new ExampleModel;
$data = $index->getExampleList();
return View::make('public.index');
}
Now the problem is i have a master layout called happypath inside layouts folder in my views which yields this public.index content and i need to pass this $data to layouts.happypath. How do i do this ?
You can use a view composer for example:
namespace App\Providers;
use App\ExampleModel;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
protected $exampleModel;
public function __construct(ExampleModel $exampleModel)
{
$this->exampleModel = $exampleModel;
}
public function boot()
{
view()->composer('layouts.happypath', function ($view) {
$view->with('publicIndex', $this->exampleModel->getExampleList());
});
}
public function register()
{
//
}
}
So, every time you use/render the layouts.happypath the $publicIndex variable will be attached within the layout. Also you need to add the ComposerServiceProvider class in your config/app.php file in the providers array. You may access/reference the data using $publicIndex variable in your layout. There are other ways like global shared $information using view()->share(...) method to share a peace of data all over the views but this may help you.
I could not figure out the ComposerServiceProvider View::composer thing. So i basically solved it like this in Laravel 4.2. Added this code to the BaseController.php
protected $menuList;
public function __construct() {
$response = API::pool([
['GET', API::url('level')],
]);
$index = new Index();
$index->setCourseList($response[0]->json()['Category']);
$result = $index->getCourseList();
View::share('result', $result); //This line shares the $result globally across all the views in laravel 4.2
}
This can be done with a Service Provider. You can either use an existing one or create a new one.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\ExampleModel;
class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
$index = new ExampleModel;
$data = $index->getExampleList();
view()->share('public.index', $data);
}
public function register()
{
}
}
Source: EasyLaravel.com

Resources