Laravel Model, deleted callback not being called - php

im trying to run some additional code when a row is deleted using my Model. However the callback statis::deleted simply isn't being triggered.
Controller:
/**
* #param Website $website
* #param Request $request
* #return \Illuminate\Http\RedirectResponse
* #throws \Exception
*/
public function delete(Website $website, Request $request)
{
$id = $request->input('id-value');
WebsiteRedirects::query()->where(['website_id' => $website['id'], 'id' => $id])->delete();
Session::flash('message', [ 'is-success' => [ '1 Redirect has been deleted!' ] ]);
return back();
}
Model:
class WebsiteRedirects extends Model
{
protected $table = 'website_redirects';
protected $guarded = [];
public $timestamps = false;
protected static function boot()
{
parent::boot();
static::saved(function ($redirect) {
PlannerStatus::status('redirect', $redirect->website_id, 1);
});
static::deleted(function($redirect) {
dd('deleted');
PlannerStatus::status('redirect', $redirect->website_id, 1);
});
}
...
static::saved works fine, and I insert using query too.
WebsiteRedirects::query()->create(
[
'website_id' => $website->id,
'redirect_from' => $request->input('redirect-from'),
'redirect_to' => $request->input('redirect-to')
]
);

The event is not being called because you are not deleting the row via Eloquent. You are deleting the row directly, without fetching the result - therefor Eloquent can't run the deleted event.
You will have to fetch the model before deleting for the event to be triggered.
WebsiteRedirects::where(['website_id' => $website['id'], 'id' => $id])->first()->delete();
Add first() to retrieve the WebsiteRedirect before you run delete()

In your code
WebsiteRedirects::query()->where(['website_id' => $website['id'], 'id' => $id])
right before the delete() method, the instance of the object is Illuminate\Database\Eloquent\Builder not your model. wich will trigger the Eloquent delete (DB) not your model's one.

Normaly you would do something like:
$user = User::find($id);
$user->delete();

Related

Laravel model observer updated & deleted events are not trigerring?

Observer
class FileLogObserver
{
public function updated(FileLogs $fileLogs)
{
$fileChangeLogs = FileChangeLogs::firstWhere('fileId', $fileLogs->filedId);
if ( !empty($fileChangeLogs)) {
$fileChangeLogs->save([
'logDetails' => '1 file updated',
]);
}
}
}
Controller
class FileLogController extends Controller
{
public function update(Request $request,$id){
$validator = Validator::make(
$request->all(),
[
'orderId' => 'required|integer',
'fileId' => 'required|integer',
'status' => 'required|string'
]
);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
$data = FileLogs::find($id);
if($data){
$data->orderId=$request->orderId;
$data->fileId=$request->fileId;
$data->status=$request->status;
$data->update();
return response()->json(['status'=>'success','StatusCode'=> 200,'message'=>'Successfully Updated','data'=>$data]);
}
else{
return response()->json(['status'=>'Failed','message'=>'Update Failed'],400);
}
}
}
The created & retrieved methods are being properly triggered. However, the updated & deleted methods not triggered. Gone through many links & read that a reason can be becoz the update is not directly on the model. so, i tried like below in my controller. But update function is not working this method. I'm using Laravel-8 version. Any help is much appreciated.
$data = FileLogs::find($id);
if($data){
$data->update(['$data->orderId'=>'$request->orderId','$data->fileId'=>'$request->fileId','$data->status'=>'$request->status']);
you need to register those observer in App\Providers\EventServiceProvider
like
/**
* Register any events for your application.
*
* #return void
*/
public function boot()
{
FileLogs::observe(FileLogObserver::class);
}
ref link https://laravel.com/docs/8.x/eloquent#observers

$touches property on Eloquent model really slows down testing

Laravel Version: 7.26.1
PHP Version: 7.4.9
Database Driver & Version: MySql 8.0.21
Description:
Am I working on a project that mostly consists of belongsTo and hasMany relationships which I cache using a trick I learned a long time ago from this post Laravel Model Caching. Now the problem is when I run a test PHPUnit freezes or maybe takes to a long time to run a single test because I waited for an hour plus, but if I comment the the $touches property the test runs just fine. Now I cant comment out all the $touches property in all my models every time I want to test so my question is, what do I do, is it possible to turn it off during testing?
Steps To Reproduce:
Model
<?php
namespace App;
use App\Contracts\CacheableModelInterface;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Country extends Model implements CacheableModelInterface
{
use Searchable,
Concerns\HasSlug,
Concerns\HasCache,
Concerns\HasManyRegions,
Concerns\HasManyProvinces,
Concerns\HasManyLocalGovernmentAreas,
Concerns\HasManyCities,
Concerns\HasManyVillages;
/**
* The relationships that should be touched on save.
*
* #var array
*/
protected $touches = ['regions', 'provinces', 'localGovernmentAreas', 'cities', 'villages'];
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'longitude', 'latitude', 'iso_code', 'calling_code'
];
/**
* Get the indexable data array for the model.
*
* #return array
*/
public function toSearchableArray()
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}
Repository
public function getRelationshipBelongingTo(string $name, string $relationship)
{
return ($this->model->where("name->".app()->getLocale(), $name)
->firstOrFail())
->{$relationship};
}
Controller
// CountryController.php
...
public function provinces(string $locale, string $name)
{
try {
$this->checkLocale($locale);
app()->setLocale($locale);
$provinces = $this->repository
->getRelationshipBelongingTo($name, 'cached_provinces');
return response()->json([
'success' => true,
'provinces' => new ProvinceCollection($provinces)
]);
} catch (ModelNotFoundException $exception) {
return response()->json([
'success' => false,
'message' => "No country named '{$name}' was found in the {$this->localeFullName($locale)} database."
]);
} catch (InvalidLocaleException $exception) {
return response()->json([
'success' => false,
'message' => $exception->getMessage()
]);
}
}
Test
/**
* #test
*/
public function can_return_provinces_belonging_to_country()
{
$country = $this->createCountry();
// Region is going to be needed in the factory when creating Province
// files so we need to have at least one present.
factory(\App\Region::class, 1)->create();
$provinces = $country->provinces()->saveMany(factory(\App\Province::class, 3)->make());
$response = $this->getJson($this->route."/{$country->name}/provinces");
$response->assertJsonStructure([
'success', 'provinces'
]);
$responseProvinces = $response->json('provinces');
$this->assertEquals($provinces->count(), collect($responseProvinces)->count());
$response->assertOk();
}

Laravel created event returning the wrong record

I'm currently using Laravel observers to implement events in my project, however, I ran into some problem where the created event returns a wrong record, for example, I create a record called Like that has post_id set to 2 and user_id set to 1, so the laravel created event should return this record right? except it returns a record where post_id is set to 0 and user_id set to 1.
my LikeObserver class:
class LikeObserver
{
/**
* Handle the like "created" event.
*
* #param \App\Like $like
* #return void
*/
public function created(Like $like)
{
dd($like);
$postId = $like->post_id;
Post::find($postId)->increment('likes_count');
}
}
as you can see whenever i dump the newly created record it returns this:
my LikeController class:
class LikeController extends Controller
{
public function insert(Request $request)
{
if(Like::where('user_id','1')->find($request->post_id))
{
return;
}
$like = Like::create(['post_id'=>$request->post_id,'user_id' => '1']);
}
public function remove(Request $request)
{
Like::where('user_id',auth()->user()->id)->findOrFail($request->post_id)->delete();
}
}
I pass post_id set to 2, however, Laravel returns the newly created record with post_id set to 0.
class LikeController extends Controller
{
public function insert(Request $request)
{
$like = Like::firstOrCreate([
'user_id' => '1',
'post_id' => $request->post_id,
]);
if(! $like->wasRecentlyCreated) {
return;
}
}
public function remove(Request $request)
{
Like::where([
'user_id' => auth()->user()->id,
'post_id' => $request->post_id,
])->first()->delete();
}
}
okay so apparently the fix was to use the creating event instead of the created event... this does return the correct record
public static function boot()
{
parent::boot();
static::creating(function ($like){
//returns the correct record.
dd($like);
});
}

Adding filter to eloquent resource to attach relationship conditionally

Laravel 5.8
PHP 7.4
I want to load the relationships conditionally like
http://127.0.0.1:8000/api/posts
and
http://127.0.0.1:8000/api/posts/1 are my end points now, I want to load comments like
http://127.0.0.1:8000/api/posts/?include=comments and
http://127.0.0.1:8000/api/posts/1/?include=comments
If the query parameter is there, only then it should load comments with posts or it should load only posts/post
I am doing this by referring a blog post
now, RequestQueryFilter
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class RequestQueryFilter
{
public function attach($resource, Request $request = null)
{
$request = $request ?? request();
return tap($resource, function($resource) use($request) {
$this->getRequestIncludes($request)->each(function($include) use($resource) {
$resource->load($include);
});
});
}
protected function getRequestIncludes(Request $request)
{
// return collect(data_get($request->input(), 'include', [])); //single relationship
return collect(array_map('trim', explode(',', data_get($request->input(), 'include', [])))); //multiple relationships
}
}
and in helper
<?php
if ( ! function_exists('filter') ) {
function filter($attach)
{
return app('filter')->attach($attach);
}
}
?>
in PostController
public funciton show(Request $request, Post $post) {
return new PostResource(filter($post));
}
but when I am trying to retrieve
http://127.0.0.1:8000/api/posts/1/?include=comments getting no comments, with no error in log
A work around will be PostResource
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'name' => $this->title,
'body' => $this->content,
];
$filter = $request->query->get('include', '');
if($filter){
$data[$filter] = $this->resource->$filter;
}
return $data;
}
I want to load the relationships conditionally like
Lazy Eager Loading using the load() call
The Lazy Eager Loading accomplishes the same end results as with() in Laravel, however, not automatically. For example:
?include=comments
// Get all posts.
$posts = Post::without('comments')->all();
if (request('include') == 'comments')) {
$posts->load('comments');
}
return PostResource::collection($posts);
Alternativelly, you could require the include query string to be an array:
?include[]=comments&include[]=tags
// Validate the names against a set of allowed names beforehand, so there's no error.
$posts = Post::without(request('includes'))->all();
foreach (request('includes') as $include) {
$posts->load($include);
}
return PostResource::collection($posts);
The call without() is only required in case you defined your model to automatically eager load the relationships you want to conditionally load.
With all data filtered in Controller, just make sure to display only loaded relations in your PostResource
public function toArray($request) {
$data = [...];
foreach ($this->relations as $name => $relation)
{
$data[$name] = $relation;
}
return $data;
}
I would create a custom resource for the posts with
php artisan make_resource
command.
E.g. PostResource.
The toArray function of the resource must return the data.
PostResource.php
public function toArray($request){
$data =['title' => $this->resource->title,
'body' => $this->resource->body,
'images' => new ImageCollection($this->whenLoaded('images')),
];
$filter = $request->query->get('filter', '');
if($filter){
$data['comments'] => new CommentCollection($this->resource->comments);
}
return $data;
}
Also, for collections, you need to create a ResourceCollection.
PostResourceCollection.php
class PostResourceCollection extends ResourceCollection
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection,
];
}
}
In your controller:
PostsController.php
//show one post
public function show(Post $post, Request $request)
{
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResource($post));
}
//list of posts
public function index(Request $request)
{
$posts = Post::all();
/**this response is for API or vue.js if you need to generate view, pass the resource to the view */
return $this->response->json( new PostResourceCollection($posts));
}
Partial Solution
It will need a small change in resource class
public function toArray($request)
{
// return parent::toArray($request);
$data = [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'comments' => new CommentCollection($this->whenLoaded('comments')),
'images' => new ImageCollection($this->whenLoaded('images')),
];
return $data;
}
and it will load comments and images if loaded and that depends on the include query parameter, if that is not included, it will not load the relationship.
However,
In post collection
return [
'data' => $this->collection->transform(function($post){
return [
'id' => $post->id,
'title' => $post->title,
'body' => $post->body,
'comments' => new CommentCollection($post->whenLoaded('comments')),
'images' => new ImageCollection($post->whenLoaded('images')),
];
}),
];
will results in
"Call to undefined method App\Models\Customer::whenLoaded()",, if anyone suggests a complete solution, it will be a great help, if I will able to do, it I will update here.

Validating a Unique Slug on Update in Laravel 5

I currently have a model that has a text field and a slug field.
I validate that the slug is unique in my form request class:
public function rules()
{
return [
'name' => 'required|min:3',
'slug' => 'required|alpha_dash|unique:questions'
];
}
This works fine on create and properly denies the creation of duplicate slugs. However on my update method, it won't let me save a record because the slug already exists. Of course the slug does exist, but it exists on the record being edited, so I would like to continue to allow it to be saved. However, it should not be able to be changed to a slug on ANOTHER record.
Here's what my update ArticlesController method looks like:
public function update(Article $article, ArticleRequest $request)
{
$article->update($request->all());
return redirect('articles');
}
Is there a way to make this work in L5?
Try to modify your rule like following(in form request class):
public function rules()
{
return [
'name' => 'required,min:3',
'slug' => 'required|alpha_dash|unique:categories,slug,'.$this->id')
];
}
It works for me.
In unique rule you may specify id you want to ignore.
You can create 2 separate request (one for create and one for update), but you can do it also this way checking if if is set(I assume your update url looks like /questions/2 ):
public function rules()
{
$rules = [
'name' => 'required|min:3',
'slug' => ['required', 'alpha_dash']
];
$rule = 'unique:questions';
$segments = $this->segments();
$id = intval(end($segments));
if ($id != 0) {
$rule .= ',slug,' . $id;
}
$rules['slug'][] = $rule;
return $rules;
}
}
If you must have the ability to update a slug, projects I've worked on usually require it is not editable after creation, then you can use laravel's built in rule to ignore a certain record on the table by primary key.
$rules['slug'] = "required|unique:questions,slug,{$id}";
http://laravel.com/docs/5.0/validation
see "Forcing a unique rule to ignore a given ID"
In EditArticleRequest:
public function $rules ()
{
$id = $this->id;
return [
'name' => 'required|min:3',
'slug' => "required|alpha_dash|unique:articles,slug,$id",
];
}
Here is how I do it in Laravel 5.3 in details:
1- Create a new Form Request class by executing the next command in your terminal:
php artisan make:request ArticleFormRequest
Where ArticleFormRequest is the name of the form request class. This command will create a file called ArticleFormRequest.php in app/Http/Requests directory.
2- Open that created file and remove its content then place the next content in it:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Article;
class ArticleFormRequest extends FormRequest
{
protected $rules = [
'name' => 'required|min:3',
'slug' => 'required|alpha_dash|unique:articles,slug',
];
// protected $user; // in case you want the current authenticated user
protected $request_method;
protected $id;
public function __construct(Request $request)
{
// $request->user() returns an instance of the authenticated user
// $this->user = $request->user(); // in case you want the current authenticated user
// $request->method() returns method of the request (GET, POST, PUT, DELETE, ...)
$this->request_method = strtoupper($request->method());
// segments(): Returns an array containing all of the segments for the request path
// it is important to assign the returned "segments" array to a variable first before using it, otherwise an error will occur
$segments = $request->segments();
// note this way will be valid only if "id" of the element is the last segment
$this->id = end($segments);
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = $this->rules;
if ($this->request_method == "POST") {
// do nothing..
} elseif (in_array($this->request_method, ["PUT", "PATCH"])) {
$article = Article::find($this->id);
if ($article) {
// forcing a unique rule to ignore a given id | https://laravel.com/docs/5.3/validation
$rules["slug"] = [
"required",
"alpha_dash",
Rule::unique("articles", "slug")->ignore($article->id, "id"),
];
// this is also can be used
// $rules['slug'] = "required|alpha_dash|unique:articles,slug,$article->id,id";
}
}
return $rules;
}
}
3- In your controller, you can use that ArticleFormRequest in store() and update() methods like this:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ArticleFormRequest;
class ArticlesController extends Controller
{
public function store(ArticleFormRequest $request)
{
// your code here..
}
public function update(ArticleFormRequest $request, $id)
{
// Your code here..
}
}
As already mentioned you can use the ignore feature in the validator functionality.
Just reference the id of the item you wish to ignore and make sure that when you update you use a patch request!
See more info here! http://laravel.com/docs/5.0/validation#rule-unique
protected $rules = [
'name' => 'required|min:3',
'slug' => 'required|alpha_dash|unique:questions'
];
public function rules()
{
$rules = $this->rules;
if ($this->isMethod('patch'))
{
$id = $this->articles;
$rules['slug'] = $rules['slug'].',slug,'.$id;
}
return $rules;
}

Categories