LadyDeathKZN's avatar

Detach and unlink images

Hi,

I have a pivot table that is storing the image_path and product_id. I can update and store with no issues. I want to know how to unlink and delete these pivot table entries at the same time, so the records get deleted and the image itself.

Controller

public function destroy($id)
    {
        $product = Product::find($id);
        unlink(public_path($product->main_image));
        $product->attributesProducts()->detach();
        $product->imageProducts()->detach();
        $product->delete();
        return redirect()->route('products.index')->with('error', 'Product deleted successfully');
    }

Product Model

public function imageProducts()
    {
        return $this->belongsToMany(ImageProduct::class, 'image_products', 'product_id', 'image_path');
    }
0 likes
12 replies
clubcouleurs's avatar

Use this :

// Detach a single image for a product ... $product->images()->detach($imageId);

// Detach all images for the given product ... $product->images()->detach();

LadyDeathKZN's avatar

mmm I have no issues with detaching its the unlink (delete) of the file itself

ismaile's avatar

What about:

$images = $product->imageProducts()->pluck('image_path');
Storage::delete($images);
$product->imageProducts()->detach();

assuming an import at the top using use Storage;

The idea here is to get an array of all image paths for a specific product and use a single call to Storage::delete to delete all images. Here is the documentation related to Storage::delete: https://laravel.com/docs/6.x/filesystem#deleting-files

PS: Some adjustments might be necessary to target the proper directory depending on what you store in image_path

LadyDeathKZN's avatar

I am getting this: SQLSTATE[HY000]: General error: 1 ambiguous column name: image_path (SQL: select "image_path" from "image_products" inner join "image_products" on "image_products"."id" = "image_products"."image_path" where "image_products"."product_id" = 1)

Here is the store function not sure if this will help:

//Images
        foreach ($request->images as $image) {
            $filename = 'cyberware-'.$image->getClientOriginalName();
            $location = public_path('storage/frontend/products/extras/'.$filename);
            Image::make($image)->save($location);
            $imagepath = Storage::url('frontend/products/extras/'.$filename);

            $product->imageProducts()->sync($imagepath);
        }

Sorry I have never done this before so don't even understand what the error is about

ismaile's avatar

Ok, I get it. what is your business logic ?

From what I understood, based on your belongsToMany, a product can have many images and a same image can belong to many products. Don't you prefer to prevent a same image to belong to many products ?

LadyDeathKZN's avatar

Yes I would prefer that, how do I do this? I got the same error with a foreach on a view:

SQLSTATE[HY000]: General error: 1 ambiguous column name: main.image_products.product_id (SQL: select "image_products".*, "image_products"."product_id" as "pivot_product_id", "image_products"."image_path" as "pivot_image_path" from "image_products" inner join "image_products" on "image_products"."id" = "image_products"."image_path" where "image_products"."product_id" = 1) (View: G:\cyber\resources\views\frontend\shop\product.blade.php)

@foreach($product->imageProducts as $image)
                    @if($image->product_id = $product->id)
                        <div class="owl-item" style="width: 540px;">
                            <div class="item">
                                <img src="{{asset($image->image_path)}}" alt="shirt">
                            </div>
                        </div>
                    @endif
@endforeach

is this related?

ismaile's avatar

What I guess is that you apply a belongsToMany to a pivot table. Indeed, the class ImageProduct is probably the model for the pivot table.

Since you would prefer to allow products to have many images and the images are unique to the products.

That means you should go for a One to Many relationship using hasMany and wihtout a pivot table.

You would only have a products table and a table for images. Here is an example of fields for the images table:

-id
-product_id
-image_path

Here is the code for the method that should replace imageProducts:

public function images()
    {
        return $this->hasMany(Image::class);
    }

So, an Image model should be created if not already.

EDIT

Your product.blade.php would then work with:

@foreach($product->images as $image)
     <div class="owl-item" style="width: 540px;">
         <div class="item">
             <img src="{{asset($image->image_path)}}" alt="shirt">
         </div>
     </div>
@endforeach

This would be the code for deleting:

$images_path = $product->images()->pluck('image_path');
Storage::delete($images_path);
$product->images()->delete();

And finally, in the store method, you would replace:

$product->imageProducts()->sync($imagepath);

with:

$product->images()->create(['image_path' => $imagepath]);

And you would replace:

$imagepath = Storage::url('frontend/products/extras/'.$filename);

with

$imagepath = 'public/frontend/products/extras/'.$filename;

I assume you created a symbolic link at public/storage which points to the storage/app/public directory using:

php artisan storage:link

PS: I have not tested the code myself

LadyDeathKZN's avatar

Hi,

Thank you for the reply! I have tested the code in several ways but get errors. Not sure what I have done but my entire products controller and blades are acting oddly. Will redo all and try again, so may bug you sometime again.

LadyDeathKZN's avatar

I have cleared the view, route, cache and config via the CLI. But this won't even find the records and it is there:

App\ImageProduct::where('product_id', '=', $product->id)->count()

I have tried DB::table, nothing, the foreach as well. Any idea what could be doing this? The table is indeed the same name image_products.

ismaile's avatar

If you are using image_products, that means you are still using the Many to Many relationship. By the way, if you follow Laravel conventions, the table should be called: image_product as explained here: https://laravel.com/docs/6.x/eloquent-relationships#many-to-many

I thought you didn't want images to belong to many products.

So, one to many or many to many ?

LadyDeathKZN's avatar

Hi, I fixed my code. Not sure what the heck went wrong. Your delete method for the file does not work, I have also tried the unlink with it and it is returning no path.

The update and store works now, thought the update does not delete the other files.

LadyDeathKZN's avatar
LadyDeathKZN
OP
Best Answer
Level 1

Hey I found a work around, I sadly don't have much time on this project:

public function update(Request $request, $id)
    {
        $product = Product::find($id);
        if($request->hasfile('main_image')){
            /* unlink(public_path($product->main_image)); */
            $image = $request->file('main_image');
            $filename = 'cyberware-'.$image->getClientOriginalName();
            $location = public_path('storage/frontend/products/'.$filename);
            Image::make($image)->save($location);
            $product->main_image = Storage::url('frontend/products/'.$filename);
        }
        $product->title = $request->title;
        $product->alt = $request->alt;
        $product->slug = $request->slug;
        $product->excerpt = $request->excerpt;
        $product->description = $request->description;
        $product->specifications = $request->specifications;
        $product->features = $request->features;
        $product->price = $request->price;
        $product->onsale_price = $request->onsale_price;
        $product->stock = $request->stock;
        $product->type = $request->type;
        $product->featured = $request->featured;
        $product->status = $request->status;
        $product->save();

        //Images
        if($request->hasfile('images') && $product->save()) {

            foreach(DB::table('image_products')->where('product_id', '=', $product->id)->get() as $imagepath){
                unlink(public_path($imagepath->image_path));
                DB::table('image_products')->where('product_id', '=', $product->id)->delete();
            }
            foreach($request->images as $image){
                $filename = $image->getClientOriginalName();
                $location = public_path('storage/frontend/products/extras/'.$filename);
                Image::make($image)->save($location);
                if(DB::table('image_products')->where('product_id', '==', null)){
                    DB::table('image_products')->insert([
                        'image_path' => Storage::url('frontend/products/extras/'.$filename),
                        'product_id' => $product->id,
                    ]);
                } else {
                    DB::table('image_products')->where('product_id', '==', $product->id)->update([
                        'image_path' => Storage::url('frontend/products/extras/'.$filename),
                        'product_id' => $product->id,
                    ]);
                }
            }
        }

        //Tags, Categories & Attributes
        if($product->save()){
            $product->tags()->sync($request->tags);
            $product->categories()->sync($request->category);
            $product->attributes()->sync($request->attribute);
        }

        return redirect()->route('products.index',)->with('warning', 'Product updated successfully');
    }

Please or to participate in this conversation.