Laravel 7从数据库和磁盘中删除阵列或单个图像-从数据库或磁盘中删除帖子,但不删除关联的图像

问题描述

在Laravel 7中,我有一个任务管理应用程序。我可以上传任务(如果是博客,则为帖子)和图像。我有多个图像上传按预期方式工作。当需要删除一个任务时,该任务可以删除,但是图像保留在数据库和磁盘中,而磁盘和磁盘则公开在名为task-images的文件夹中。作为Laravel的新手,我正在努力解决这个问题。我试图更改filesystem.PHP中的设置(我将用注释掉的代码发布该设置),但这并没有像我期望的那样更改位置。最后,我希望能够在删除帖子时删除多个图像,并单击单个图像上的“删除”,然后从数据库和磁盘中删除该图像。我正在为所有任务路由使用资源控制器。我不知道该如何处理,发现的教程并没有真正解决我的特定问题。任何帮助将不胜感激。预先谢谢你。

这是我在TaskController.PHP的任务控制器

<?PHP

namespace App\Http\Controllers;

use App\Task;
use App\Image;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

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 = 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);
                $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);
        // $image = '/task-images/' . $task->image;
        Storage::delete($task->image);
        $task->delete();
        return redirect('home')->with('success','Task Deleted');
    }
}

filesystem.PHP(仅磁盘部分)

 'disks' => [

        'local' => [
            'driver' => 'local','root' => storage_path('app'),// 'root' => public_path('task-images'),],

...

在我自己的表演模板中,show.blade.PHP已完成,以防代码冲突。

@extends('layouts.master')
@section('content')
<div class="container">
    <a href="/home" class="btn bg-purple mb-4">Go Back</a>
    <div class="card p-3">
        <div class="row">
            <div class="col-md-4 col-sm-12">
                <h3>Task</h3>
                <p>{{ $task->task_name }}</p>
                <h3>Assigned On:</h3>
                <p>{{ $task->created_at->format('m/d/Y') }}</p>
                <h3>Assigned To:</h3>
                <p>{{ $task->task_assigned_to }}</p>
            </div>
            <div class="col-md-4 col-sm-12">
                <h3>Task Description</h3>
                <p>{{ $task->task_description }}</p>
                <h3>Priority</h3>
            <p>{{ $task->task_priority }}</p>
                <h3>Status</h3>
                <p>{{ $task->task_status }}</p>
            </div>
            <div class="col-md-4 col-sm-12">
                <h3>Test Environment Date:</h3>
                <p>{{ $task->task_to_be_completed_date }}</p>
                <h3>Notes</h3>
                <p>{{ $task->task_notes }}</p>
                <h3>Action</h3>
                <div style="display: inline;">
                    <a href="/tasks/{{$task->id}}/edit" class="btn btn-sm btn-primary mr-2">
                        <i class="fa fa-edit"></i> Edit
                    </a>
                </div>
            <form style="display: inline;" action="/tasks/{{ $task->id }}" method="POST" class="">
                        @csrf
                        @method('DELETE')
                      <button type="submit" class="btn btn-danger btn-sm ml-1 mr-1">
                        <i class="fa fa-trash"></i> Delete
                      </button>
                    </form>

            </div>
            <div class="col-md-12">
                <h5>Images</h5>
                <hr />
                  <div class="row">
                      @if($task->image->count()>0)

                          @for($i=0; $i < count($images = $task->image()->get()); $i++)
                          <div class="col-lg-4 col-md-6 col-sm-12">
                            <a href="#" class="thumbnail" data-toggle="modal" data-target="#lightBox"><img class="w-50 mb-2" src="/task-images/{{ $images[$i]['name'] }}" alt=""></a>
                            <form style="display: inline;" action="/tasks/{{ $task->name }}" method="POST" class="">
                                @csrf
                                @method('DELETE')
                              <button type="submit" class="btn btn-danger btn-sm ml-1 mr-1">
                                <i class="fa fa-trash"></i> Delete
                              </button>
                            </form>
                        </div>

                          @endfor
                          @else
                              <p>No images found</p>
                      @endif
                  </div>
                <br />
              </div>
        </div>
    </div>
</div>
<!--Modal Start-->

<div id="lightBox" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
    <div class="modal-dialog">
        <button type="button" class="close hidden" data-dismiss="modal" aria-hidden="true">×</button>
        <div class="modal-content">
            <div class="modal-body">
                <img class="w-100" src="" alt="" />
            </div>
        </div>
    </div>
</div>

<!--Modal End-->
@endsection
@section('scripts')
<script>
    $(document).ready(function() {
     var $lightBox = $('#lightBox');

     $('[data-target="#lightBox"]').on('click',function(event) {
         var $img = $(this).find('img'),src = $img.attr('src'),alt = $img.attr('alt'),css = {
                 'maxWidth': $(window).width() - 100,'maxHeight': $(window).height() - 100
             };

         $lightBox.find('.close').addClass('hidden');
         $lightBox.find('img').attr('src',src);
         $lightBox.find('img').attr('alt',alt);
         $lightBox.find('img').css(css);
     });

     $lightBox.on('shown.bs.modal',function (e) {
         var $img = $lightBox.find('img');

         $lightBox.find('.modal-dialog').css({'width': $img.width()});
         $lightBox.find('.close').removeClass('hidden');
     });
 });
 </script>
@endsection

在我的任务模型Task.PHP中,我有

<?PHP

namespace App;

use Illuminate\Database\Eloquent\Model;
use App\Image;

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('App\Image');
        return $this->hasMany(Image::class);
    }
}

最后是我的图像模型Image.PHP

<?PHP

namespace App;

use Illuminate\Database\Eloquent\Model;
use App\Task;

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('App\Task','task_id');
        return $this->belongsTo(Task::class);
    }
}

如果我缺少什么,请告诉我,以便我编辑问题。再次感谢您提前帮助我解决此问题。我整个星期都在为此挠头。干杯。

修改 在按照以下建议在模型中实现启动功能后,我收到一个错误,指出无效参数用于foreach。我跑了一个dd($ task);下图显示了结果。

dd of $task on delete

最终编辑 下面的答案适合我的情况。我确实需要编辑一些东西来最终确定分辨率: 在Task.PHP中,我将foreach更改为以下内容

foreach($task->image ?: [] as $image) 

我在模型中声明了图像而不是图像,这导致了问题。添加三元运算符也有助于代码不会引发任何错误

在我的TasksController.PHP中,我使用相同的三元运算符更改了更新和创建函数,如下所示:

 if ($request->hasFile('images')) {
            $files = $request->file('images');
            foreach ($files ?: [] as $file) {
                $name = time() . '-' . $file->getClientOriginalName();
                $name = str_replace(' ',$name);
                $task->image()->create(['name' => $name]);
            }
        }

我希望这可以帮助其他遇到相同问题的人。感谢@GrumpyCrouton和@lagBox解决此问题以及@ user3563950提供的帮助 没有他们,我还是会再挣扎几个星期。

解决方法

在您的App\Image类上,使用以下命令实现启动功能;

use Illuminate\Support\Facades\Storage;

public static function boot() {
    parent::boot();
    self::deleting(function($image) {
        Storage::delete(Storage::path($image->name));
    });
}

还可以在App\Task类中实现启动方法

use Illuminate\Support\Facades\Storage;

public static function boot() {
    parent::boot();
    self::deleting(function($task) {
        foreach($task->images as $image) {
         $image->delete();
        }
    });
}

现在在您的TaskController上实现如下的destroy方法;

public function destroy($id)
    {
        $task = Task::findOrFail($id);
        $task->delete();
        return redirect('home')->with('success','Task Deleted');
    }

作为奖励,学习Laravel model binding可以减轻使用findOrFail()查找实例的痛苦