I am running Laravel 7 and have a list of tasks (would be posts if it were a blog) and I need to make sure that when the task is deleted, that all subsequent images are deleted in both the database and in the disc. When I click the delete button, the page throws an error: Invalid argument supplied for foreach()
.
Unfortunately, this is a vague error and can be caused by a variety of causes. My hopes are that someone can take a look at my code and see if I am missing something. I am relatively new to Laravel so tracking down this issue has been more than a challenge. Than you in advance for helping my work out this issue.
In my Task.php
model, I have:
<?php namespace App; use IlluminateDatabaseEloquentModel; use AppImage; use IlluminateSupportFacadesStorage; class Task extends Model { protected $fillable = [ 'task_name', 'task_priority', 'task_assigned_to', 'task_assigned_by', 'task_description', 'task_to_be_completed_date', 'task_status', 'task_notes' ]; public function user() { return $this->belongsTo(User::class); } public function image() { // return $this->hasMany('AppImage'); return $this->hasMany(Image::class); } public static function boot() { parent::boot(); self::deleting(function ($task) { foreach ($task->images as $image) { $image->delete(); } }); } }
In my Image.php
model, I have
<?php namespace App; use IlluminateDatabaseEloquentModel; use IlluminateSupportFacadesStorage; use AppTask; class Image extends Model { protected $fillable = [ 'task_id', 'name', ]; protected $uploads = '/task-images/'; public function getFileAttribute($image) { return $this->uploads . $image; } public function task() { // return $this->belongsTo('AppTask', 'task_id'); return $this->belongsTo(Task::class); } public static function boot() { parent::boot(); self::deleting(function ($image) { Storage::delete(Storage::path($image->name)); }); } }
In my TasksController.php
, (all code in case something is causing a conflict here) here is what I have:
<?php namespace AppHttpControllers; use AppTask; use AppImage; use IlluminateHttpRequest; use IlluminateSupportFacadesAuth; use IlluminateSupportFacadesFile; use IlluminateSupportFacadesStorage; class TasksController extends Controller { public function index() { $tasks = Task::orderBy('created_at', 'desc')->paginate(10); return view('/tasks')->with('tasks', $tasks); } public function create() { return view('tasks.create'); } public function store(Request $request) { $this->validate($request, [ 'task_name' => 'required', 'task_description' => 'required', ]); // Create Task $user = Auth::user(); $task = new Task(); $data = $request->all(); $task->user_id = $user->id; $task = $user->task()->create($data); if ($request->hasFile('images')) { $files = $request->file('images'); foreach ($files as $file) { $name = time() . '-' . $file->getClientOriginalName(); $name = str_replace(' ', '-', $name); $file->move('task-images', $name); $task->image()->create(['name' => $name]); $images = new Image; $images->name = $name; } } $task->task_name = $request->input('task_name'); $task->task_description = $request->input('task_description'); $task->task_priority = $request->input('task_priority'); $task->task_assigned_by = $request->input('task_assigned_by'); $task->task_assigned_to = $request->input('task_assigned_to'); $task->task_to_be_completed_date = $request->input('task_to_be_completed_date'); $task->task_notes = $request->input('task_notes'); $task->task_status = $request->task_status; $task->save(); return redirect('/home')->with('success', 'Task Created'); } public function edit($id) { $task = Task::find($id); return view('tasks.edit', ['task' => $task]); } public function update(Request $request, $id) { $this->validate($request, [ 'task_name' => 'required', 'task_description' => 'required', ]); $task = Task::find($id); $task->task_name = $request->input('task_name'); $task->task_description = $request->input('task_description'); $task->task_priority = $request->input('task_priority'); $task->task_assigned_by = $request->input('task_assigned_by'); $task->task_assigned_to = $request->input('task_assigned_to'); $task->task_to_be_completed_date = $request->input('task_to_be_completed_date'); $task->task_notes = $request->input('task_notes'); $task->task_status = $request->input('task_status'); if ($request->hasFile('images')) { $files = $request->file('images'); foreach ($files as $file) { $name = time() . '-' . $file->getClientOriginalName(); $name = str_replace(' ', '-', $name); $file->move('task-images', $name); $task->image()->create(['name' => $name]); } } $task->update(); return redirect('/home')->with('success', 'Task Updated'); } public function show($id) { $task = Task::find($id); return view('tasks.show')->with('task', $task); } public function destroy($id) { $task = Task::findOrFail($id); // dd($task); $task->delete(); return redirect('home')->with('success', 'Task Deleted'); } } And in the home page where I am calling the delete function, `home.blade.php`, I have: @extends('layouts.master') @section('content') <div class="custom-container"> <div class="row justify-content-center"> <div class="col-md-12"> @include('layouts.includes.messages') <div class="card w-100"> <div class="card-header text-white" style="background-color: #605ca8;"> <h3 class="card-title">Tasks</h3> <div class="card-tools"> <a href="tasks/create" class="btn btn-success"> <i class="fas fa-tasks"></i> Add New Task </a> </div> </div> <!-- /.card-header --> </div> <div class="row"> <div class="col-12"> <div class="card"> <div class="card-header"> <h3 class="card-title">Ongoing Tasks</h3> <div class="card-tools"> <div class="input-group input-group-sm" style="width: 150px;"> <input type="text" name="table_search" class="form-control float-right" placeholder="Search"> <div class="input-group-append"> <button type="submit" class="btn btn-default"><i class="fas fa-search"></i></button> </div> </div> </div> </div> <!-- /.card-header --> <div class="card-body table-responsive p-0"> <table class="table table-hover text-nowrap"> <thead> <tr> <th>Task</th> <th>Priority</th> <th>Assigned To</th> <th>Test Environment Date</th> <th>Status</th> <th>Action</th> </tr> </thead> <tbody> @if($tasks->count() > 0) @foreach($tasks as $task) <tr> <td><a href="/tasks/{{$task->id}}">{{ $task->task_name }}</a></td> <td>{{ $task->task_priority }}</td> <td>{{ $task->task_assigned_to }}</td> <td>{{$task->task_to_be_completed_date }}</td> <td>{{ $task->task_status }}</td> <td> <a href="tasks/{{$task->id}}/edit" class="btn btn-primary btn-sm mr-2"> <i class="fa fa-edit"></i> Edit </a> <form action="tasks/{{$task->id}}" method="POST" style="display: inline" class=""> @csrf @method('DELETE') <button type="submit" class="btn btn-sm btn-danger ml-1 mr-1"> <i class="fa fa-trash"></i> Delete </button> </form> </td> </tr> @endforeach @else <p class="ml-4 pt-2">No Tasks Found. Please Add one.</p> @endif </tbody> </table> </div> <!-- /.card-body --> </div> <!-- /.card --> </div> </div> </div> {{ $tasks->links() }} </div> </div> @endsection
In my filesystem.php under the config folder, I have(just for storage):
'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), // 'root' => public_path('task-images'), ],
If I missed anything or codes, please let me know so I can edit my question. Thank you again in advance for helping me with this issue.
Advertisement
Answer
You are trying to access a property named images
on your instance of Task
but there isn’t an attribute named images
and there isn’t a relationship named images
, so null
is being returned: $task->images == null
. You named your relationship image
not images
, though images
would be more correct since this relationship can return many; plural. Change the name of the relationship to images
:
public function images() { return $this->hasMany(Image::class); }
Or reference it by its current name: $task->image