Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ufodisko's avatar

How do I store 'article_id' in 'images' table before that article has been created?

I have 2 tables: articles and images #articles: id, user_id, title, body #images: id, original_name, filename, article_id

I am trying to upload multiple images using Dropzone on the create article view. The problem is the Dropzone plugin submits the image to the database as soon as I drag/drop it into the box, even before creating the article to be able to get its id.

So far the upload is working, but I can't associate it with an article_id. I've tried associating it with Auth::user()->id and that worked.

How do I go about achieving this?

In my ArticleController

    public function syncTags(Article $article, array $tags) {
            $article->tags()->sync($tags);
        }
    
        /**
         * @param ArticleRequest|BlogRequest $request
         * @return mixed
         */
        public function createArticle(Requests\ArticleRequest $request) {
    
            $article = Auth::user()->articles()->create($request->all());
    
            $this->syncTags($article, $request->input('tag_list'));
    
            return $article;
        }
    
        public function postUpload() {
            $photo = Input::all();
            $response = $this->image->upload($photo);
            return $response;
    
        }

In Article Model

    public function image() {
            return $this->hasMany('App\Image');
        }

In Image Model

    public function article() {
            return $this->belongsTo('App\Article');
        }

ImageRepository

    <?php
    namespace App\Logic\Image;
    
    use Auth;
    use App\Article;
    use App\Image;
    use Illuminate\Support\Facades\Validator;
    use Illuminate\Support\Facades\Response;
    use Illuminate\Support\Facades\Config;
    use Illuminate\Support\Facades\File;
    use Intervention\Image\ImageManager;
    
    class ImageRepository
    {
        public function upload( $form_data )
        {
            $validator = Validator::make($form_data, Image::$rules, Image::$messages);
            if ($validator->fails()) {
                return Response::json([
                    'error' => true,
                    'message' => $validator->messages()->first(),
                    'code' => 400
                ], 400);
            }
            //$articleId = $article->id;
            $photo = $form_data['file'];
            $originalName = $photo->getClientOriginalName();
            $originalNameWithoutExt = substr($originalName, 0, strlen($originalName) - 4);
            $filename = $this->sanitize($originalNameWithoutExt);
            $allowed_filename = $this->createUniqueFilename( $filename );
            $filenameExt = $allowed_filename .'.jpg';
            $uploadSuccess1 = $this->original( $photo, $filenameExt );
            $uploadSuccess2 = $this->icon( $photo, $filenameExt );
            if( !$uploadSuccess1 || !$uploadSuccess2 ) {
                return Response::json([
                    'error' => true,
                    'message' => 'Server error while uploading',
                    'code' => 500
                ], 500);
            }
            $sessionImage = new Image;
            $sessionImage->filename      = $allowed_filename;
            $sessionImage->original_name = $originalName;
            //$sessionImage->article_id = $articleId;
            $sessionImage->save();
            return Response::json([
                'error' => false,
                'code'  => 200
            ], 200);
        }
        public function createUniqueFilename( $filename )
        {
            $full_size_dir = Config::get('images.full_size');
            $full_image_path = $full_size_dir . $filename . '.jpg';
            if ( File::exists( $full_image_path ) )
            {
                // Generate token for image
                $imageToken = substr(sha1(mt_rand()), 0, 5);
                return $filename . '-' . $imageToken;
            }
            return $filename;
        }
        /**
         * Optimize Original Image
         */
        public function original( $photo, $filename )
        {
            $manager = new ImageManager();
            $image = $manager->make( $photo )->encode('jpg')->save(Config::get('images.full_size') . $filename );
            return $image;
        }
        /**
         * Create Icon From Original
         */
        public function icon( $photo, $filename )
        {
            $manager = new ImageManager();
            $image = $manager->make( $photo )->encode('jpg')->resize(200, null, function($constraint){$constraint->aspectRatio();})->save( Config::get('images.icon_size')  . $filename );
            return $image;
        }
        /**
         * Delete Image From Session folder, based on original filename
         */
        public function delete( $originalFilename)
        {
            $full_size_dir = Config::get('images.full_size');
            $icon_size_dir = Config::get('images.icon_size');
            $sessionImage = Image::where('original_name', 'like', $originalFilename)->first();
            if(empty($sessionImage))
            {
                return Response::json([
                    'error' => true,
                    'code'  => 400
                ], 400);
            }
            $full_path1 = $full_size_dir . $sessionImage->filename . '.jpg';
            $full_path2 = $icon_size_dir . $sessionImage->filename . '.jpg';
            if ( File::exists( $full_path1 ) )
            {
                File::delete( $full_path1 );
            }
            if ( File::exists( $full_path2 ) )
            {
                File::delete( $full_path2 );
            }
            if( !empty($sessionImage))
            {
                $sessionImage->delete();
            }
            return Response::json([
                'error' => false,
                'code'  => 200
            ], 200);
        }
        function sanitize($string, $force_lowercase = true, $anal = false)
        {
            $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
                "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
                "—", "–", ",", "<", ".", ">", "/", "?");
            $clean = trim(str_replace($strip, "", strip_tags($string)));
            $clean = preg_replace('/\s+/', "-", $clean);
            $clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
            return ($force_lowercase) ?
                (function_exists('mb_strtolower')) ?
                    mb_strtolower($clean, 'UTF-8') :
                    strtolower($clean) :
                $clean;
        

        }
    }

routes.php

    Route::resource('articles', 'ArticlesController');
    
    // dropzone upload
    Route::post('upload', ['as' => 'upload-post', 'uses' =>'ArticlesController@postUpload']);
    Route::post('upload/delete', ['as' => 'upload-remove', 'uses' =>'ArticlesController@deleteUpload']);

And this is create.blade.php - I was compelled to include it as I'm using 2 forms on the same page. Can this even work?

    {!! Form::open(['url' => 'articles', 'id' => 'frm']) !!}
            <p>
                {!! Form::label('title', 'Title:') !!}
                {!! Form::text('title', null, ['class' => 'form-control']) !!}
            </p>
    
            <div class="editor">
                <p>
                    {!! Form::label('body', 'Body:') !!}
                    {!! Form::textarea('body', null, ['class' => 'form-control', 'id' => 'myEditor']) !!}
                </p>
            </div>
    
            <p>
                {!! Form::label('published_at', 'Date:') !!}
                {!! Form::input('date', 'published_at', date('Y-m-d'), ['class' => 'form-control']) !!}
            </p>
    
            <p>
                {!! Form::label('tag_list', 'Tags:') !!}
                {!! Form::select('tag_list[]', $tags, null, ['class' => 'form-control', 'multiple']) !!}
            </p>
    
            <p>
                {!! Form::submit('Submit Article', ['id' => 'submit']) !!}
            </p>
        {!! Form::close() !!}
    
        <div class="row">
            <div class="col-md-offset-1 col-md-10">
                <div class="jumbotron how-to-create" >
    
                    <h3>Images <span id="photoCounter"></span></h3>
                    <br />
    
                    {!! Form::open(['url' => route('upload-post'), 'class' => 'dropzone', 'files'=>true, 'id'=>'real-dropzone']) !!}
    
                    <div class="dz-message">
    
                    </div>
    
                    <div class="fallback">
                        <input name="file" type="file" multiple />
                    </div>
    
                    <div class="dropzone-previews" id="dropzonePreview"></div>
    
                    <h4 style="text-align: center;color:#428bca;">Drop images in this area  <span class="glyphicon glyphicon-hand-down"></span></h4>
    
                    {!! Form::close() !!}
0 likes
14 replies
rettigd's avatar

The way I approached this was to upload the files to a temp folder (you could hash the filenames on user id and model type to avoid name collisions). Then save the file name(s) to the session. When the form is submitted, get the files names from the session (move the files to their final resting place) and create your models. Also, I have a cleanup process to delete abandoned uploads if they never click submit.

ufodisko's avatar

@razerdeathadder that didn't help.

@rettigd mind sharing an example? My problem is not only how do I get the article_id before submitting the form, but also, how do I tell ImageRepository.php to include the $article->id while saving the image? Because $article->id gives an error that Argument 2 passed to App\Logic\Image\ImageRepository::upload() must be an instance of App\Article

        $sessionImage = new Image;
        $sessionImage->filename      = $allowed_filename;
        $sessionImage->original_name = $originalName;
        $sessionImage->article_id = 3;
        $sessionImage->save();

EDIT: I figured how to pull the article details into the ImageRepository, by using the Article facade Article::findOrFail(2) that would pull an array of the article data into the article_id field which of course gives an error. Still lots of errors, but hopefully one step closer to the solution.

Snapey's avatar

Either disable uploads and prompt the user that they must save before uploads are permitted, or create a post immediately you show the edit page. The disadvantage of this of course is that you could have some abandoned records.

another option is to generate a random guid and then pass that to your edit form. all file uploads can then be tagged with that reference number and when the post is saved, either add the guid as a column on your post record or search for it in your images for it and replace it with the actual post ID ( ie create a random ID assign it to the photos then fix it when the post is created)

ufodisko's avatar

another option is to generate a random guid and then pass that to your edit form. all file uploads can then be tagged with that reference number and when the post is saved, either add the guid as a column on your post record or search for it in your images for it and replace it with the actual post ID ( ie create a random ID assign it to the photos then fix it when the post is created)

How do I assign the article_id to the image and post with the same guid? And shouldn't the article guid be the same as the ones of the images so I can match the 2? How do I do that? Can you show me an example please?

Snapey's avatar

I'm on my IPad at the moment so writing code is really time consuming.

in your posts controller either create a guid (there are packages available) or use a simple Unix time stamp

pass this to your view and in the view add it to a hidden field (imageKey)

add the same value to your dropzone data

images are posted and the imageKey is persisted along with the image metadata

When the post request comes in, store the image key in the database against the post

You can either go back to your images, search for the key and replace it with the post id or just leave them be. If leaving the random key then change the belongsTo relationship in your Photos model to reference this column and not the default which is to use the post id

jekinney's avatar

I use the str_rand() to generate guids of various lengths.

With that said you may only have two options if you don't want to save the files as temp or wait to post the images until a click event (click save button).

  1. if an article hasn't been created, create one with null fields or default data.
  2. set the guid when the page loads and use that as a key to link your images to the article. Though using an id like article_id is the typical schema doesn't mean you have to do that way. You just need a unique identifier for reference.
bgallagh3r's avatar

Generally I would have the user create the article before they can upload images, even if it is a "draft", the other alternative is to use UUID's for the articles, this way you could generate a UUID when you load the form, and pass it along when the user uploads the images, then when the user saves the article the UUID will already have been created.

Snapey's avatar

everyone figures it out in their own way. Several options are presented here. If none of them work for you then start a new question.

Please or to participate in this conversation.