Laravel Nova – Manually Send Error Alert From Observer Class - laravel

I have a Season Resource model with a field named active.
The requirement is to disable deletion for a season with an active status.
I have created an Observer for the season model to watch deleting an event. From this function, I can block the delete in case active is true.
But the issue is with the error message; is there any way to add an error message to session flash from the Observer class?
<?php
public function deleting(Season $season)
{
if($season->active_season)
{
Log::info('Sorry, this season can`t be deleted.
There must be at least one active season.');
}
return false;
}

This is not tested but I was able to achieve something like that in a previous project:
use Illuminate\Validation\ValidationException;
class AbcObserver
{
public function creating(Abc $abc)
{
if ($abc->details != 'test') {
throw ValidationException::withMessages(['details' => 'This is not valid.']);
}
}
}

You can use the Exception class. I tested it in a Nova action and it throws the toast error notification.
use Exception;
throw new Exception('Error message here ...');
// Or
throw_if(
$validator->fails(), // or any true boolean
Exception::class,
'Error message here ...'
);

I don't know how to flash the error message.
But since the requirement is to disable deletion for a season with an active status, I'm suggesting to use policy which won't display the delete icon when doesn't match the condition.
class SeasonPolicy {
...
public function delete(User $user, Season $season) {
if($season->active_season) {
return false;
}
return true;
}
}
and register the policy in AuthServiceProvider.
Note:
Undefined Policy Methods
If a policy exists but is missing a method for a particular action,
the user will not be allowed to perform that action. So, if you have
defined a policy, don't forget to define all of its relevant
authorization methods.

Related

Attempt to read property "view" on null, laravel reset email notificaiton

When I try to send reset password notification, this error occurs
Attempt to read property "view" on null
this is the code line on which this error occurs
$this->notify(new ResetPasswordNotification($token));
this is my code inside notification class
You need this method in User model:
public function sendPasswordResetNotification($token): void
{
$this->notify(new ResetPassword($token, $this));
}
You must pass user with token
You need to return the MailMessage in toMail like so:
return $this->buildMailMessage($this->resetUrl($notification));

Laravel 5.3 Passing AuthorizationException a message when a Policy fails

I'm trying to find a clean way to override the AuthorizationException to take a dynamic string that can be passed back when a Policy fails.
Things I know I can do are:
Wrap the Policy in the Controller with a try-catch, then rethrow a custom exception that takes a specific string, which seems a bit verbose
abort(403, '...') in the Policy prior to returning, which seems a bit hacky since policies are already doing the work
and then in /Exceptions/Handler::render I can send back the response as JSON
Is there a nicer way to do this to get a message in the response of a policy failure? Or is 1 or 2 my best choices.
I noticed if you throw AuthorizationException($message) in a policy using Laravel's exception it jumps you out of the policy, but continues execution in the controller, and doesn't progress to Handler::render. Which I'm assuming this is them handling the exception somehow, but I couldn't find where they were doing it... so if anyone finds where this is happening I'd still like to know.
If you create your own AuthorizationException and throw it, it will stop execution as expected, and drop into Handler::render so I ended up adding this method to my policy:
use App\Exceptions\AuthorizationException;
// ... removed for brevity
private function throwExceptionIfNotPermitted(bool $hasPermission = false, bool $allowExceptions = false, $exceptionMessage = null): bool
{
// Only throw when a message is provided, or use the default
// behaviour provided by policies
if (!$hasPermission && $allowExceptions && !is_null($exceptionMessage)) {
throw new \App\Exceptions\AuthorizationException($exceptionMessage);
}
return $hasPermission;
}
New exception for throwing in policies only in \App\Exceptions:
namespace App\Exceptions;
use Exception;
/**
* The AuthorizationException class is used by policies where authorization has
* failed, and a message is required to indicate the type of failure.
* ---
* NOTE: For consistency and clarity with the framework the exception was named
* for the similarly named exception provided by Laravel that does not stop
* execution when thrown in a policy due to internal handling of the
* exception.
*/
class AuthorizationException extends Exception
{
private $statusCode = 403;
public function __construct($message = null, \Exception $previous = null, $code = 0)
{
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
}
Handle the exception and provide the message in a JSON response in Handler::render():
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException && $request->expectsJson()) {
return response()->json([
'message' => $exception->getMessage()
], $exception->getStatusCode());
}
return parent::render($request, $exception);
}
and I also removed it from being logged in Handler::report.
What I found was not "passing" a custom message to authorize, just defining a custom message in the policy it selfs, so, for example, if you have the method "canUseIt", in your UserPolicy, like the following:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
return false;
}
You can change it and do something like this:
public function canUseIt(User $user, MachineGun $machineGun)
{
if ($user->isChuckNorris()) {
return true;
}
$this->deny('Sorry man, you are not Chuck Norris');
}
It uses the deny() method from the HandlesAuthorization trait.
Then when you use it like $this->authorize('canUseIt', $user) and it fails, it will return a 403 HTTP error code with the message "Sorry man, you are not Chuck Norris".
Laravel does have an option to pass arguments to customize the errors in the authorize() method of a Controller Class accessed through the Gate Class's implementation of the GateContract made available by the Gate Facade.
However, it seems that they forgot to pass those arguments to the allow()/deny() methods responsible for returning error messages, implemented in the HandlesAuthorization Trait.
You need to pass those arguments by following these steps:
Modify the authorize method in the vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php file
public function authorize($ability, $arguments = []) {
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny($arguments);
}
Call authorize from the controller with an extra argument, ie: your custom $message -
$message = "You can not delete this comment!";
$response = $this->authorize('delete', $message);
I have made a pull request to fix this, hopefully someone will merge it soon.
I think the best way to think about Policies is they are simply a way to split controller logic, and move all authorization related logic to a separate file. Thus abort(403, 'message') is the right way to do this, in most cases.
The only downside is you may want some policies to be 'pure' logic for use in business logic only, and thus not to have any response control. They could be kept separate, and a commenting system could be used distinguish them.

Checking passed object existence in Laravel delayed queue job

I believe if I pass an Eloquent object to a delayed Laravel job a "findOrFail" method is called to "restore" the object and pass it to the controller of my Job class.
The problem is that the DB record representing the object might be gone by the time the job is actually processed.
So "findOrFail" aborts before even calling the "handle" methods.
Everything seems fine. The problem is that the job now "gets transferred" to the failed jobs list. I know I can remove it from there manually, but that doesn't sound right.
Is there a way to "know" in my job class directly that the passed object "failed to load" or "does not exist" or anything similar?
Basically I would like to be able to do something if "ModelNotFoundException" is thrown while "rebuilding" my passed objects.
Thank you
SOLUTION:
Based on Yauheni Prakopchyk's answer I wrote my own trait and used it instead of SerializesModels where I need my altered behaviour.
Here's my new trait:
<?php
namespace App\Jobs;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Database\ModelIdentifier;
use Illuminate\Database\Eloquent\ModelNotFoundException;
trait SerializesNullableModels
{
use SerializesModels {
SerializesModels::getRestoredPropertyValue as parentGetRestoredPropertyValue;
}
protected function getRestoredPropertyValue($value)
{
try
{
return $this->parentGetRestoredPropertyValue($value);
}
catch (ModelNotFoundException $e)
{
return null;
}
}
}
And that's it - now if the model loading fails I still get a null and can decide what to do with it in my handle method.
And if this is only needed in a job class or two I can keep using original trait everywhere else.
If i'm not mistaken, you have to override getRestoredPropertyValue in your job class.
protected function getRestoredPropertyValue($value)
{
try{
return $value instanceof ModelIdentifier
? (new $value->class)->findOrFail($value->id) : $value;
}catch(ModelNotFoundException $e) {
// Your handling code;
}
exit;
}

Trouble with multiple model observers in Laravel

I'm stuck on a weird issue. It feels like in Laravel, you're not allowed to have multiple model observers listening to the same event. In my case:
Parent Model
class MyParent extends Eloquent {
private static function boot()
{
parent::boot();
$called_class = get_called_class();
$called_class::creating(function($model) {
doSomethingInParent();
return true;
}
}
}
Child Model
class MyChild extends myParent {
private static function boot()
{
parent::boot();
MyChild::creating(function($model) {
doSomethingInChild();
return true;
}
}
}
In the above example, if I do:
$instance = MyChild::create();
... the line doSomethingInChild() will not fire. doSomethingInParent(), does.
If I move parent::boot() within the child after MyChild::creating(), however, it does work. (I didn't confirm whether doSomethingInParent() fires, but I'm presuming it doesn't)
Can Laravel have multiple events registered to Model::creating()?
This one is tricky. Short version: Remove your return values from you handlers and both events will fire. Long version follows.
First, I'm going to assume you meant to type MyParent (not myParent), that you meant your boot methods to be protected, and not private, and that you included a final ) in your create method calls. Otherwise your code doesn't run. :)
However, the problem you describe is real. The reason for it is certain Eloquent events are considered "halting" events. That is, for some events, if any non-null value is returned from the event handlers (be it a closure or PHP callback), the event will stop propagating. You can see this in the dispatcher
#File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
public function fire($event, $payload = array(), $halt = false)
{
}
See that third parameter $halt? Later on, while the dispatcher is calling event listeners
#File: vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php
foreach ($this->getListeners($event) as $listener)
{
$response = call_user_func_array($listener, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ( ! is_null($response) && $halt)
{
array_pop($this->firing);
return $response;
}
//...
If halt is true and the callback returned anything that's not null (true, false, a sclaer value, an array, an object), the fire method short circuits with a return $response, and the events stop propagating. This is above and beyond that standard "return false to stop event propagation". Some events have halting built in.
So, which Model events halt? If you look at the definition of fireModelEvent in the base eloquent model class (Laravel aliases this as Eloquent)
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
protected function fireModelEvent($event, $halt = true)
{
//...
}
You can see a model's events default to halting. So, if we look through the model for firing events, we see the events that do halt are
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
$this->fireModelEvent('deleting')
$this->fireModelEvent('saving')
$this->fireModelEvent('updating')
$this->fireModelEvent('creating')
and events that don't halt are
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
$this->fireModelEvent('booting', false);
$this->fireModelEvent('booted', false);
$this->fireModelEvent('deleted', false);
$this->fireModelEvent('saved', false);
$this->fireModelEvent('updated', false);
$this->fireModelEvent('created', false);
As you can see, creating is a halting event, which is why returning any value, even true, halted the event and your second listener didn't fire. Halting events are typically used when the Model class wants to do something with the return value from an event. Specifically for creating
#File: vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
protected function performInsert(Builder $query)
{
if ($this->fireModelEvent('creating') === false) return false;
//...
if you return false, (not null) from your callback, Laravel will actually skip performing the INSERT. Again, this is different behavior from the standard stop event propagation by returning false. In the case of these four model events, returning false will also cancel the action they're listening for.
Remove the return values (or return null) and you'll be good to go.

Clean way of checking if resource exists and if user can edit it

I have two very common steps that I have to repeat in almost every CRUD method in my Controllers. I have my Users split into 2 groups ( Users, Administrators ). Now Users can edit, update and delete only their own entries while admins can do all the CRUD operations.
The second piece of code I find my self writing every time is checking if the resource exist which is repetitive and somewhat annoying.
Here is what I attempted:
<?php
class BaseController extends Controller
{
// Received Eloquent model each model has user_id field
public function authorize($resource)
{
// Check if currently logged in users id matches user_id
// value of the resource
if($resource->user_id !== CurrentUser::getUser()->id)
{
// Users id does not match with resource user_id check if user is admin
if(!CurrentUser::getGroup() === 'Admin')
{
// The id's do not match and user is not admin redirect him back to root
Session::flash('error', 'You cannot edit this resource');
return Redirect::to('/');
}
}
}
}
class CarController extends BaseController
{
public function edit($id)
{
// Attempt to find the resource
$car = Car::find($id);
// Check if found
if(!$car)
{
// Resource was not found
Session::flash('error', 'Resource was not found');
return Redirect::to('/cars');
}
// First check if user is allowed to edit the resource
// this however does not work because returned Redirect is simply ignored I would
// have to return boolean and then check it but...
$this->authorize($car);
// ... rest of the code
}
}
This would not be a problem if I had 3-4 methods but I have some 6-10 methods and as you can see this part takes some 20 lines of code add that 6-10 times not to mention it's repetitive to the point where it get's annoying.
I have tried to solve the problem using a filter but the problem is that I can pass the id to the filter but not get it to work in a way that I would pass the model as well.
There has to be a cleaner way to implement all this. I'm somewhat happy with authorize function/process but it would be awesome not having to call is every time possibly having some filter and each controller would define global variable/array of methods that require authorization.
As for checking if record was found I was hoping maybe a filter could be done to catch all RecordNotFound exceptions and redirect back to controllers index route with a message.
You can use findOrFail() and catch the exception in your BaseController and you also have two options:
try
{
$post = $this->post->findOrFail($id);
return View::make('posts.show', compact('post'));
}
catch(ModelNotFoundException $e)
{
return Redirect::route('posts.index');
}
Or
$post = $this->post->findOrFail($id);
return View::make('posts.show', compact('post'));
And a exception handler returning back to your form with the input:
App::error(function(ModelNotFoundException $exception)
{
return Redirect::back()->withErrors()->withInput();
});
Note that those are just examples, not took from your code.

Resources