facebook google plus twitter
Webucator's Free PHP Tutorial

Lesson: Uploading Files

Welcome to our free PHP tutorial. This tutorial is based on Webucator's Introduction to PHP Training course.

In some cases, you may need to collect files from website users. You can do this using the file input type. But you'll want to be careful not to just allow any type or any size file to be uploaded.

Lesson Goals

  • To upload images through a form to the server.

Mac Users... move on...

If you are on a Mac, you can move on to the next activity. If you're on Windows, read on...

In this lesson, we make use of the built-in PHP function mime_content_type(). For this function to work on Windows, you must enable the php_fileinfo.dll extension, which is already included with MAMP, but is not enabled by default.

  1. Open your php.ini file in your editor. If you don't remember where the php.ini file is, visit http://localhost:8888/Webucator/php/PHPBasics/Demos/find-php.ini.php in your browser.
  2. Add the following line of code in the section on extensions:
    extension=php_fileinfo.dll
    It should look something like this:php_fileinfo.dll extension
  3. Restart the MAMP Servers so that your new php.ini settings take effect.

Now you should be all set to continue with the lesson.

Uploading Images via an HTML Form

To upload files via an HTML form, the form tag's method must be set to "post" and the enctype attribute must be set to "multipart/form-data" as shown below:

<form method="post" enctype="multipart/form-data">

The following example demonstrates how to safely allow the user to upload an image to the server:

Code Sample:

UploadingFiles/Demos/file-upload.php
---- C O D E   O M I T T E D ----
<?php
  if (empty($_POST['submitted'])) {
?>
<h1>Upload Form</h1>
<form method="post" action="file-upload.php"
      enctype="multipart/form-data">
  <label for="image-name">Image Name:</label>
  <input name="image-name" id="image-name">
  <label for="image">Image:</label>
  <input type="file" name="image" id="image" accept=".png,.jpg">
  <button name="submitted" value="1" class="wide">Submit</button>
</form>
<?php
} else {
  //process the  form
  $errors = [];
  if (!$_POST['image-name']) {
    $error = 'Image name is required.';
  } else {
    $imgFileName = $_FILES['image']['name'];
    $imgTmpLocation = $_FILES['image']['tmp_name'];
    $fileSize = $_FILES['image']['size'];
    $fileError = $_FILES['image']['error'];

    $ext = strtolower(end(explode('.', $imgFileName)));
    $time = time();
    $imgNewName=$_POST['image-name'] . '_' . $time . '.' . $ext;
    $fileSavePath = 'uploaded-images/' . $imgNewName;
  
    $allowedTypes = ['image/png','image/jpeg'];
    $mimeType = mime_content_type($imgTmpLocation);

    if (!in_array($mimeType, $allowedTypes)) {
      $error = 'You uploaded a file of type ' . $mimeType .
        ' Only png and jpg files are allowed.';
    } elseif ($fileError === UPLOAD_ERR_INI_SIZE) {
      $error = "The file is too big.";
    } elseif ($fileError) {
      $error = "The file could not be uploaded.";
    } elseif (!move_uploaded_file($imgTmpLocation, $fileSavePath)) {
      $error = "Could not save file.";
    }
  }

  if ($error) {
    echo "<h2>Error</h2>
      <p>$error</p>";
  } else {
    echo "<h2>Success</h2>
      <p>Your image has been uploaded.</p>";
  }
}
?>
---- C O D E   O M I T T E D ----

Code Explanation

  1. The first thing to notice about this page is that it submits to itself. It uses $_POST['submitted'] to know whether or not the form has been submitted. The first time the page is loaded, it will show the form. When the form is submitted, it will attempt to upload and save the uploaded picture.
  2. The form includes an input field of type file that is used to browse for the file to upload. Notice the accept attribute. This is used to tell the browser what file extensions are accessible. This can take a comma-delimited list (e.g., .png,.jpg) to specify multiple acceptable extensions.
  3. The form also includes an image-name field, which is combined with the value of time() and the extension of the uploaded file to name the uploaded file:
    $ext = strtolower(end(explode('.', $imgFileName)));
    $time = time();
    $imgNewName=$_POST['image-name'] . '_' . $time . '.' . $ext;
    $fileSavePath = 'uploaded-images/' . $imgNewName;
    • The built-in end() function gets the last element of an array.
    • Using the value of time() as part of the file name ensures that each file name will be unique.
  4. We then get the mime type of the file using the built-in mime_content_type() function:
    $allowedTypes = ['image/png','image/jpeg'];
    $mimeType = mime_content_type($imgTmpLocation);
    
    if (!in_array($mimeType, $allowedTypes)) {
      $error = 'You uploaded a file of type ' . $mimeType .
        ' Only png and jpg files are allowed.';
    }
    is is a secure way of checking that the file is the type of file you're expecting. You cannot rely on the extension of the file, as that can be modified.
  5. Uploaded files are stored in the $_FILES superglobal array. Because our file input field is named "image", this file is stored by PHP as $_FILES['image']. The file includes the following keys:
    • tmp_name - The temporary name of the uploaded file, which we will use when we move the file to the uploaded-images directory.
    • size - The size of the uploaded file.
    • type - The type of the uploaded file as reported by the browser. As this can easily be faked by hackers, we won't use this.
    • error - Any error that prevented the file from being uploaded. You can check for specific errors using the constants documented at https://www.php.net/manual/features.file-upload.errors.php. Our code specifically checks for UPLOAD_ERR_INI_SIZE, so that we can give a relevant error message and then just checks for any other error to report a generic error message.
      elseif ($fileError === UPLOAD_ERR_INI_SIZE) {
        $error = "The file is too big.";
      } elseif ($fileError) {
        $error = "The file could not be uploaded.";
      }
  6. We then use the built-in move_uploaded_file() function to move the uploaded file to the uploaded-images folder:
    elseif (!move_uploaded_file($imgTmpLocation, $fileSavePath)) {
      $error = "Could not save file.";
    }
  7. Finally, we either report the error if there is one or success if there is not.

post_max_size and MAX_FILE_SIZE

PHP provides two related directives that can affect file uploads:

  1. The php.ini file includes a related directive: post_max_size. This sets the maximum total size of all data, including any uploaded files, that can be sent to the server in a single post. If a post is larger than the value of post_max_size, no error will occur, but the $_FILES and $_POST arrays will both be emptied, so all the posted data will be lost. For more information on post_max_size, see https://www.php.net/manual/ini.core.php#ini.post-max-size.
  2. PHP provides a way of setting the maximum file size allowed on a form-by-form basis through a hidden input field with the name "MAX_FILE_SIZE" and a value in bytes. For more information on MAX_FILE_SIZE, see https://www.php.net/manual/features.file-upload.post-method.php.

Resizing Images

There are three functions built in to PHP that take a path to an image and from it create an image identifier, which can then be used to manipulate and save over or create a new copy of the original image:

  1. imagecreatefromjpeg(filename) - creates an image identifier for a JPEG image.
  2. imagecreatefrompng(filename) - creates an image identifier for a PNG image.
  3. imagecreatefromgif(filename) - creates an image identifier for a GIF image.

The following demo shows how to use the first two of these to take an image, resize it with the built-in imagescale() function, and then save it as a new image:

Code Sample:

UploadingFiles/Demos/resize-image.php
---- C O D E   O M I T T E D ----
<h1>Resize Image</h1>
<img src="images/pooh.jpg" alt="Winnie the Pooh">
<img src="images/bulb.png" alt="Light Bulb">
<form method="post" action="resize-image.php">
  <label for="width" class="inline">Width:</label>
  <input type="number" min="50" max="500" step="25"
    id="width" name="width" value="200">
  <label for="height" class="inline">Height:</label>
  <input type="number" min="50" max="500" step="25"
    id="height" name="height" value="300">
  <button name="resizing" value="1">Resize</button>
</form>
<?php
  if (isset($_POST['resizing'])) {
    $w = $_POST['width'];
    $h = $_POST['height'];
    echo '<h1>Resized Images</h1>';
    $poohOrigPath = 'images/pooh.jpg';
    $poohNewPath = "images/pooh-$w-$h.jpg";
    $bulbOrigPath = 'images/bulb.png';
    $bulbNewPath = "images/bulb-$w-$h.png";

    if ($img = imagecreatefromjpeg($poohOrigPath)) {
      $newImg = imagescale($img, $w, $h);
      imagejpeg($newImg, $poohNewPath);
      imagedestroy($img);
      echo "<img src='$poohNewPath' alt='Winnie Resized'>";
    } else {
      echo 'imagecreatefromjpeg() failed: ' . $poohNewPath;
    }

    if ($img = imagecreatefrompng($bulbOrigPath)) {
      $newImg = imagescale($img, $w, $h);
      imagesavealpha($newImg, true);
      imagepng($newImg, $bulbNewPath);
      imagedestroy($img);
      echo "<img src='$bulbNewPath' alt='Bulb Resized'>";
    } else {
      echo 'imagecreatefrompng() failed: ' . $bulbNewPath;
    }
  }
?>
---- C O D E   O M I T T E D ----

Code Explanation

This page contains a form for submitting new height and width values. When the form is submitted it resizes images/pooh.jpg and images/bulb.png to the specified dimensions and creates new images with file names formatted as images/pooh-$w-$h.jpg and images/bulb-$w-$h.png.

Things to note:

  1. We initially create the images with imagecreatefromjpg() and imagecreatefrompng().
  2. We resize both types of images using imagescale($img, $w, $h).
  3. For the PNG image, we use imagesavealpha($newImg, true) to keep the transparency. PHP includes many other built-in functions for working with color and transparency.
  4. We save the new images with imagejpeg($newImg, $poohNewPath) and imagepng($newImg, $bulbNewPath).
  5. Finally, to free memory, we destroy the image identifiers with imagedestroy($img). This does not delete the original image files.

Uploading a Profile Picture

Duration: 45 to 60 minutes.

In this exercise, you will write a new uploadProfilePic() function in utilities.php.

  1. Open utilities.php from the UploadingFiles/Exercises/phppoetry.com/includes folder.
  2. Write a new uploadProfilePic() function, which should do the following:
    1. Take one parameter: $img, which takes an uploaded picture (e.g., $_FILES['image'])).
    2. Get the temporary location and save it as $imgTmpLocation.
    3. Check to make sure the mime type of the image is either 'image/png' or 'image/jpeg'. You can use the in_array() function for this. If it is not one of these mime types, the function should return false.
    4. If the passed-in image contains an error, log the error and return false.
    5. Create a name and path for the new image as profile-$time.$ext and ../../../static/images/profile-pics/profile-$time.$ext, where $ext is "png" or "jpg" and $time is the current value of time().
    6. Try to create an image from the image at $imgTmpLocation. You will have to account for the type of image passed in (PNG or JPEG).
    7. Return the new image file name.
  3. To test your code, navigate to http://localhost:8888/Webucator/php/UploadingFiles/Exercises/phppoetry.com/my-account.php, which has been created for you and upload a profile picture. Note that you will need to be logged in.

Solution:

UploadingFiles/Solutions/phppoetry.com/includes/utilities.php
---- C O D E   O M I T T E D ----
  function uploadProfilePic($img) {
    // Get the temporary location
    $imgTmpLocation = $img['tmp_name'];

    // Check mime type and file error
    $fileError = $img['error'];
    $allowedTypes = ['image/png','image/jpeg'];
    $mimeType = mime_content_type($imgTmpLocation);

    if (!in_array($mimeType, $allowedTypes)) {
      return false;
    } elseif ($fileError) {
      logError($fileError);
      return false;
    }

    // Create new image name and path
    $ext = ($mimeType === 'image/png') ? 'png' : 'jpg';
    $time = time();
    $newImgFileName = "profile-$time.$ext";
    $fileSavePath = '../../../static/images/profile-pics/' .
                    $newImgFileName;

    // Try to move the passed-in image to the new path
    try {
      if ($mimeType === 'image/png') {
        if ($img = imagecreatefrompng($imgTmpLocation)) {
          $newImg = imagescale($img, 200, 200);
          imagesavealpha($newImg, true);
          imagepng($newImg, $fileSavePath);
          imagedestroy($img);
        } else {
          logError('imagecreatefrompng() failed: ' . $fileSavePath);
          return false;
        }
      } else { // jpg
        if ($img = imagecreatefromjpeg($imgTmpLocation)) {
          $newImg = imagescale($img, 200, 200);
          imagejpeg($newImg, $fileSavePath);
          imagedestroy($img);
        } else {
          logError('imagecreatefromjpeg() failed: ' . $fileSavePath);
          return false;
        }
      }
      return $newImgFileName;
    } catch (PDOException $e) {
      logError($e->getMessage());
      return false;
    }
  }
?>

Code Explanation

Be sure to review my-account.php in your editor to make sure you understand all the code. If there are functions that are new to you (e.g., is_file()), look them up on php.net.

Optional PHP Project

The PDF that comes with this course has an additional lesson entitled "Admin Site." The lesson includes several exercises for creating an admin site for phppoetry.com. While you are not required to do those exercises, it would be excellent practice for you. Those of you who are taking this course as part of a series of courses will find it especially useful as a way of preparing for the final project.

Moving on...

If you decide to skip this for now or continue with other courses simultaneously, just give it any rating to finish up the PHP course and move on.