File and Directory Handling

In web development, especially with server-side scripting languages like PHP, managing files and directories on the server is a crucial task. This involves operations such as opening, closing, copying, renaming, deleting files, creating and manipulating directories, and handling file uploads and downloads. PHP provides a rich set of functions to perform these operations securely and efficiently.

File Open/Close Operations

Before you can read from or write to a file, you must open it. Opening a file associates a stream of data with the file, allowing your script to interact with it. Similarly, after you are done with a file, it's essential to close it to release the system resources it was using and to ensure that any buffered data is written to the file.

Opening Files with `fopen()`

The `fopen()` function is used to open a file or URL. It requires two parameters: the filename (or URL) and the mode in which to open the file. The mode determines what operations you can perform on the file (read, write, append, etc.) and where the file pointer is initially placed.

Common File Modes:
  • 'r': Read only. The pointer is placed at the beginning of the file. If the file does not exist, `fopen()` returns `FALSE`.
  • 'r+': Read and write. The pointer is placed at the beginning of the file. The file must exist.
  • 'w': Write only. Opens the file for writing only. The pointer is placed at the beginning of the file and the file is truncated (emptied) if it exists. If it does not exist, it is created.
  • 'w+': Read and write. Opens the file for reading and writing. The file is truncated if it exists, or created if it doesn't exist.
  • 'a': Append. Opens the file for writing only; the file pointer is at the end of the file. If the file does not exist, it is created.
  • 'a+': Read and append. Opens the file for reading and writing. The file pointer is at the end of the file. If the file does not exist, it is created.
  • 'x': Exclusive creation. Creates a new file. Returns `FALSE` and an error if the file already exists.
  • 'x+': Exclusive creation with read/write. Creates a new file for reading and writing. Returns `FALSE` and an error if the file already exists.

The `fopen()` function returns a file pointer resource on success, or `FALSE` on failure. It's crucial to check the return value.

Example:

$file = fopen("my_document.txt", "w");

This line attempts to open a file named "my_document.txt" in write mode. If the file exists, its content will be erased. If it doesn't exist, it will be created. The file pointer resource is stored in the $file variable.

Closing Files with `fclose()`

Once you have finished reading from or writing to a file, you should close it using the `fclose()` function. This frees up the memory and system resources associated with the file handle.

Syntax:

bool fclose ( resource $handle )

$handle is the file pointer returned by `fopen()` or other file opening functions.

Example:

fclose($file);

Assuming $file holds a valid file pointer, this command will close the associated file.

It's good practice to always close files you open, even if your script is about to end. PHP will automatically close open files when a script finishes execution, but explicit closing is cleaner and safer, especially in long-running scripts or when dealing with many files.

File Copying, Renaming, and Deletion

PHP provides straightforward functions to manage the existence and location of files on the server.

Copying Files with `copy()`

The `copy()` function copies a file. It takes the source filename and the destination filename as arguments. It returns `TRUE` on success and `FALSE` on failure.

Syntax:

bool copy ( string $source , string $destination [, resource $context ] )

Example:

if (copy("original.txt", "backup/original_backup.txt")) { echo "File copied successfully."; } else { echo "File copying failed."; }

This code attempts to copy "original.txt" to a directory named "backup" with a new name. The destination directory must exist, or the copy operation will fail.

Renaming Files with `rename()`

The `rename()` function renames a file or directory. It can also be used to move a file to a different directory by specifying a new path in the destination argument.

Syntax:

bool rename ( string $oldname , string $newname [, resource $context ] )

Example:

if (rename("draft.txt", "final_report.txt")) { echo "File renamed successfully."; } else { echo "File renaming failed."; }

This renames "draft.txt" to "final_report.txt". If you wanted to move it to another directory:

rename("documents/report.doc", "archive/2023/report.doc");

The source and destination paths can be relative or absolute. The destination directory must exist.

Deleting Files with `unlink()`

The `unlink()` function (also known as `delete()` in some contexts, though `unlink` is the standard PHP function) deletes a file. It takes the filename as an argument and returns `TRUE` on success or `FALSE` on failure.

Syntax:

bool unlink ( string $filename [, resource $context ] )

Example:

if (unlink("temporary_file.tmp")) { echo "File deleted successfully."; } else { echo "File deletion failed."; }

This will remove "temporary_file.tmp" from the server's file system.

Security Note: Always validate user-provided filenames before using them with `unlink()`, `rename()`, or `copy()` to prevent malicious users from deleting or overwriting critical files. Use functions like `basename()` to strip directory paths.

Directory Operations

Managing directories is as important as managing files. PHP allows you to create, read, delete, and change directories.

Creating Directories with `mkdir()`

The `mkdir()` function creates a directory. It takes the path of the new directory as the first argument. Optional second and third arguments can specify permissions and create parent directories if they don't exist.

Syntax:

bool mkdir ( string $pathname [, int $mode = 0777 [, bool $recursive = FALSE [, resource $context ]]] )

  • $pathname: The directory path to create.
  • $mode: Permissions (e.g., 0777 for full access for everyone, 0755 for owner read/write/execute, group and others read/execute). Defaults to 0777, but is affected by the system's `umask`.
  • $recursive: If set to `TRUE`, it will create parent directories as needed.
Example:

if (mkdir("new_folder")) { echo "Directory 'new_folder' created."; } else { echo "Failed to create directory."; }

To create nested directories:

if (mkdir("projects/2023/reports", 0755, TRUE)) { echo "Nested directories created."; } else { echo "Failed to create nested directories."; }

Reading Directory Contents with `scandir()`

The `scandir()` function returns an array of files and directories within a specified directory. It includes '.' (current directory) and '..' (parent directory).

Syntax:

array|false scandir ( string $directory [, int $sorting_order = 0 [, resource $context ]] )

  • $directory: The directory to scan.
  • $sorting_order: 0 for ascending (default), 1 for descending.
Example:

$files = scandir("uploads");

if ($files !== false) {

foreach ($files as $file) {

if ($file != "." && $file != "..") {

echo "Found: " . $file . "
";

}

}

} else { echo "Could not scan directory."; }

Deleting Directories with `rmdir()`

The `rmdir()` function removes an empty directory. It returns `TRUE` on success and `FALSE` on failure. The directory must be empty for `rmdir()` to work.

Syntax:

bool rmdir ( string $dir [, resource $context ] )

Example:

if (rmdir("old_logs")) { echo "Directory 'old_logs' removed."; } else { echo "Failed to remove directory. Ensure it is empty."; }

To remove a directory and its contents recursively, you would typically need to write a custom function that iterates through the directory, deletes all files within it, and then deletes the subdirectories before finally deleting the main directory.

Changing Directory with `chdir()`

The `chdir()` function changes the current working directory of the script.

Syntax:

bool chdir ( string $directory )

Example:

if (chdir("/var/www/html/my_app/uploads")) { echo "Changed directory to uploads."; } else { echo "Failed to change directory."; }

You can also use `getcwd()` to get the current working directory.

File Uploads

Handling file uploads is a common requirement for web applications, allowing users to upload images, documents, or other files. PHP provides built-in mechanisms to manage this through the `$_FILES` superglobal array and specific functions.

The `$_FILES` Superglobal Array

When a form with `enctype="multipart/form-data"` and an `` element is submitted, PHP populates the `$_FILES` array. This array contains information about the uploaded file(s).

The structure of `$_FILES` for a single file upload named 'myFile' looks like this:

$_FILES['myFile'] = array(
        'name'     => 'user_image.jpg',     // Original name of the file on the client machine
        'type'     => 'image/jpeg',         // MIME type of the file
        'size'     => 102400,               // Size of the file in bytes
        'tmp_name' => '/tmp/phpABCDE',      // Temporary name of the file on the server
        'error'    => 0                     // Error code associated with the file upload
    );
File Upload Error Codes:
  • UPLOAD_ERR_OK (0): No error, the file uploaded successfully.
  • UPLOAD_ERR_INI_SIZE (1): The uploaded file exceeds the upload_max_filesize directive in php.ini.
  • UPLOAD_ERR_FORM_SIZE (2): The uploaded file exceeds the MAX_FILE_SIZE hidden input field specified in the HTML form.
  • UPLOAD_ERR_PARTIAL (3): The uploaded file was only partially uploaded.
  • UPLOAD_ERR_NO_FILE (4): No file was uploaded.
  • UPLOAD_ERR_NO_TMP_DIR (6): Missing a temporary folder.
  • UPLOAD_ERR_CANT_WRITE (7): Failed to write file to disk.
  • UPLOAD_ERR_EXTENSION (8): A PHP extension stopped the file upload.

Moving the Uploaded File with `move_uploaded_file()`

Once a file is uploaded, it resides in a temporary location on the server. To make it permanent, you must move it using `move_uploaded_file()`. This function checks if the file was uploaded via HTTP POST and if it was uploaded successfully.

Syntax:

bool move_uploaded_file ( string $filename , string $destination )

  • $filename: The filename of the uploaded file (from $_FILES['userfile']['tmp_name']).
  • $destination: The path where the file should be moved.
Example: Uploading a single file

HTML Form (index.html):

<form action="upload.php" method="post" enctype="multipart/form-data">
  Select image to upload:
  <input type="file" name="profilePic" id="profilePic">
  <input type="submit" value="Upload Image" name="submit">
</form>

PHP Script (upload.php):

<?php
if (isset($_POST["submit"])) {
    $target_dir = "uploads/";
    $target_file = $target_dir . basename($_FILES["profilePic"]["name"]);
    $uploadOk = 1;
    $imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

    // Check if image file is a actual image or fake image
    $check = getimagesize($_FILES["profilePic"]["tmp_name"]);
    if($check !== false) {
        echo "File is an image - " . $check["mime"] . ".";
        $uploadOk = 1;
    } else {
        echo "File is not an image.";
        $uploadOk = 0;
    }

    // Check if file already exists
    if (file_exists($target_file)) {
        echo "Sorry, file already exists.";
        $uploadOk = 0;
    }

    // Check file size (e.g., limit to 5MB)
    if ($_FILES["profilePic"]["size"] > 5000000) {
        echo "Sorry, your file is too large.";
        $uploadOk = 0;
    }

    // Allow certain file formats
    if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
    && $imageFileType != "gif" ) {
        echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
        $uploadOk = 0;
    }

    // Check if $uploadOk is set to 0 by an error
    if ($uploadOk == 0) {
        echo "Sorry, your file was not uploaded.";
    // if everything is ok, try to upload file
    } else {
        if (move_uploaded_file($_FILES["profilePic"]["tmp_name"], $target_file)) {
            echo "The file ". htmlspecialchars( basename( $_FILES["profilePic"]["name"])). " has been uploaded.";
        } else {
            echo "Sorry, there was an error uploading your file.";
        }
    }
}
?>
Best Practices for File Uploads:
  • Always check the error code in $_FILES.
  • Validate the file type (MIME type and extension) and size.
  • Sanitize filenames using `basename()` to prevent directory traversal attacks.
  • Store uploaded files outside the web root if they are not meant to be directly accessible via URL.
  • Use unique filenames to avoid overwriting existing files (e.g., using `uniqid()` or hashing the filename).
  • Set appropriate file permissions on the destination directory.

File Downloads

Allowing users to download files from the server is also a common requirement. This involves sending the correct HTTP headers to the browser so it knows how to handle the file.

Sending HTTP Headers for Downloads

You need to send specific headers before sending the file content. The most important ones are:

  • Content-Description: File Transfer
  • Content-Type: application/octet-stream (or the specific MIME type of the file)
  • Content-Disposition: attachment; filename="your_file.ext" (tells the browser to download the file with a specific name)
  • Content-Length: sizeof(file) (the size of the file in bytes)
  • Expires: 0, Cache-Control: must-revalidate, Pragma: public (for caching control)

Reading and Sending File Content

After sending the headers, you read the file content and output it directly to the browser. It's important to use functions like `readfile()` or `file_get_contents()` combined with `echo`.

Example: Downloading a file

PHP Script (download.php):

<?php
$file_path = 'path/to/your/document.pdf'; // The actual path to the file on the server
$file_name = basename($file_path); // Get the file name

if (file_exists($file_path)) {
    // Set headers
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream'); // Generic binary stream
    header('Content-Disposition: attachment; filename="' . $file_name . '"');
    header('Expires: 0');
    header('Cache-Control: must-revalidate');
    header('Pragma: public');
    header('Content-Length: ' . filesize($file_path));

    // Clear output buffer
    flush();

    // Read the file and output it
    readfile($file_path);
    exit;
} else {
    http_response_code(404);
    echo "File not found.";
    exit;
}
?>
Determining MIME Type:

For more specific downloads, you might want to determine the file's MIME type dynamically. You can use functions like mime_content_type() (if the fileinfo extension is enabled) or create a mapping of extensions to MIME types.

$mime_type = mime_content_type($file_path);

Then use this in the Content-Type header: header('Content-Type: ' . $mime_type);

File and directory handling are fundamental aspects of server-side programming. Mastering these functions in PHP allows you to build robust applications that can manage user-uploaded content, serve downloadable files, and organize data efficiently on the server.