Search code examples
codeignitercodeigniter-4

Ci4 / Image Upload and Manipulate Error / finfo_file(/tmp/phpSrVWUZ): failed to open stream: No such file or directory


I am trying to (1) upload a image file (2) resize it and center it and (3) change the file name and (4) move it into /public dir.

Here is a part of my code;

$path = $this->request->getFile('dh_site_logo')->store();
            
$temp_file_path = WRITEPATH.'uploads/' . $path;

service('image')
    ->withFile($temp_file_path)  // must include full path
    ->fit(250,150,'center')
    ->save($temp_file_path);
            
$public_file_path = ROOTPATH.'public';
$file_to_upload = 'site_logo.'.$update_post->guessExtension();;
$overwrite = true;      
        
$update_post->move($public_file_path, $file_to_upload, $overwrite);

I assume I would have to do the image manipulation in the writable/uploads directory prior to moving to /public

I cant seem to lock on to the file that has been uploaded with a new random name, manipulate and then move.

Tried this, too

            // example of return $path = "20220130/1643544458_5a528551d1fe83c88e02.gif"
            $path = $this->request->getFile('dh_site_logo')->store('site_logo', 'site_logo.gif');
            
            $temp_file_path = WRITEPATH.'uploads/' . $path;

             service('image')
                ->withFile($temp_file_path)  // must include full path
                ->fit(250,150,'center')
                ->save($temp_file_path);
            
            $public_file_path = ROOTPATH.'public';
            $new_file_name = 'site_logo.gif';
            $overwrite = true;      
        
            $update_post->move($public_file_path, $new_file_name, $overwrite);

The above give me an error, The uploaded file has already been moved


Solution

  • Ci4 / Image Upload and Manipulate Error / finfo_file(/tmp/phpSrVWUZ): failed to open stream: No such file or directory

    Problem 1:

    You're receiving the above error because of the line of code below:

    $file_to_upload = 'site_logo.'.$update_post->guessExtension();;
    

    Explanation 1:

    On your first line of code below:

    $path = $this->request->getFile('dh_site_logo')->store();
    

    You're calling the CodeIgniter\HTTP\Files\UploadedFile::store(?string $folderName = null, ?string $fileName = null) method which saves the uploaded file (/tmp/phpSrVWUZ) to a new location.

    I.e: The store(...) method moves the uploaded file from the server's default temporary directory (/tmp) to the project's upload directory (WRITEPATH . 'uploads/').

    You then try calling $update_post->guessExtension() forgetting that the UploadedFile instance $update_post still refers to the old non-existent path (/tmp/phpSrVWUZ), hence the error.

    To be more specific, the method CodeIgniter\HTTP\Files\UploadedFile::guessExtension() calls another method CodeIgniter\Files\File::getMimeType(). The method getMimeType(...) tries to retrieve the mime type using the code snippet below on a non-existent file resulting in the error:

    finfo_file(finfo_open(FILEINFO_MIME_TYPE), "/tmp/phpSrVWUZ");
    
    // PHP Warning:  finfo_file(/tmp/phpSrVWUZ): Failed to open stream: No such file or directory in ...
    

    =========

    Problem 2:

    Tried this, too

    ...

    The above give me an error, The uploaded file has already been moved

    Explanation 2:

    You normally receive this error because you're trying to move the uploaded file to a new location more than once.

    This happens when you call the CodeIgniter\HTTP\Files\UploadedFile::store(...) or CodeIgniter\HTTP\Files\UploadedFile::move(...) method more than once.

    Excerpt from CI 4.x source code.

    /**
     * Move the uploaded file to a new location.
     * ...
     * If this method is called more than once, any subsequent calls MUST raise
     * an exception.
     * ...
     */
    public function move(...): bool {
    // ...
            if ($this->hasMoved) {
                throw HTTPException::forAlreadyMoved();
            }
    // ...
    
    }
    

    To be more specific, every UploadedFile instance has a property called protected $hasMoved = false; which gets updated to true once the uploaded file has been moved successfully from the server's default temporary directory:

    Excerpt from CI 4.x source code.

        /**
         * Returns whether the file has been moved or not. If it has,
         * the move() method will not work and certain properties, like
         * the tempName, will no longer be available.
         */
        public function hasMoved(): bool;
    

    Solution A:

    This applies if you don't care about the original uploaded file and you're only interested in the final transformed/resized file residing in the 'public' path.

    
    public function createThumbnail(\CodeIgniter\HTTP\Files\UploadedFile $uploadedFile, ?string $newThumbnailFileName = null, int $width = 250, int $height = 150, string $position = "center"): ?\CodeIgniter\Files\File
    {
        if (!$uploadedFile->isValid()) {
            return null;
        }
    
        $newThumbnailFileName = $newThumbnailFileName
            ? ((($point = strrpos($newThumbnailFileName, ".")) === false) ? $newThumbnailFileName : substr($newThumbnailFileName, 0, $point)) . $uploadedFile->guessExtension()
            : $uploadedFile->getRandomName();
    
        $targetPath = ROOTPATH . 'public' . DIRECTORY_SEPARATOR . $newThumbnailFileName;
    
        \Config\Services::image()
            ->withFile($uploadedFile->getRealPath() ?: $uploadedFile->__toString())
            ->fit($width, $height, $position)
            ->save($targetPath);
    
        return new \CodeIgniter\Files\File($targetPath, true);
    }
    
    

    The function above basically leaves the original uploaded file in the server's default temporary directory untouched and processes a new image which gets saved in the project's 'public' path.

    Usage For Solution A:

    The function above returns a CodeIgniter\Files\File instance representing the newly transformed image. I.e:

    $requestFileName = "dh_site_logo";
    $uploadedFile = $this->request->getFile($requestFileName);
    
    // Generates a thumbnail in the 'public' path with a uniquely generated filename.
    $thumbnail = $this->createThumbnail(
        uploadedFile: $uploadedFile
    );
    
    // OR
    
    // Generates a thumbnail in the 'public' path with a custom filename ('site_logo').
    $thumbnail = $this->createThumbnail(
        uploadedFile: $uploadedFile,
        newThumbnailFileName: "site_logo"
    );
    
    // OR
    
    // You can modify the default parameters as well.
    $thumbnail = $this->createThumbnail(
        uploadedFile: $uploadedFile,
        width: 100,
        height: 150,
        position: "left"
    );
    

    Solution B:

    This applies if for some reason you want to transform/resize and save the modified image in the 'public' path similar to Solution A and still persist/keep or move the original uploaded file from the server's temporary directory to your project's writable/uploads folder for future reference or purposes.

    TIP: The file will be deleted from the temporary directory at the end of the request if it has not been moved away or renamed. - Excerpt From PHP Doc: Example #2 Validating file uploads

    Steps:

    1. Generate as many thumbnails (transformed files) as you need.
    2. Lastly, move the uploaded file from the server's default temporary directory to the project's writable/uploads folder.
    
    $requestFileName = "dh_site_logo";
    $uploadedFile = $this->request->getFile($requestFileName);
    
    // 1. Generate as many thumbnails (transformed files) as you need.
    
    // Generates a thumbnail in the 'public' path with
    // a uniquely generated filename.
    $thumbnail = $this->createThumbnail(
        uploadedFile: $uploadedFile
    );
    
    // 2. Lastly, move the uploaded file from the server's default temporary
    // directory to the project's 'writable/uploads' folder (I.e: $uploadedFile->store()).
    
    if (!$uploadedFile->hasMoved()) {
        // The moved uploaded file.
        $file = new \CodeIgniter\Files\File(WRITEPATH . 'uploads/' . $uploadedFile->store(), true);
    }