Operowanie na katalogach i plikach – kilka przydatnych funkcji w PHP

File-management

Są w PHP takie rzeczy, które jeśli raz już ktoś gdzieś napisał, to później już nikt tego nie dotyka. Wierzcie mi lub nie, ale sam napisałem kilka takich klas / funkcji, do których nie chciałbym wracać. Oczywiście nie dlatego, że zostały źle napisane, a z prostego powodu – pisze się to raz, testuje oraz wykorzystuje i nie pamięta jakich funkcji konkretnie trzeba było użyć.

Przykładem takich funkcji, które wpisują się w pierwsze zdanie dzisiejszego artykułu są operacje na pojedynczych plikach oraz całym systemie plików. Postaram się Wam dzisiaj pokazać kilka ciekawych (tak myślę!) operacji, które pozwolą stworzyć sobie małą bibliotekę do zarządzania plikami lub katalogami.

Usuniecie pojedynczego pliku / rekursywne usuniecie całego katalogu

/**
 * Usuniecie pliku / rekursywne usuniecie calego katalogu
 *
 * @param string $path sciezka do pliku / katalogu do usuniecia
 * @return void
 */
function deleteRecursive($path) {
    if (is_dir($path)) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isDir()) {
                rmdir($file->getPathname());
            } else {
                unlink($file->getPathname());
            }
        }

        rmdir($path);
    } else {
        unlink($path);
    }
}

Kopiowanie pojedynczego pliku / rekursywne kopiowanie katalogu

/**
 * Kopiowanie pojedynczego pliku / rekursywne kopiowanie katalogu
 *
 * @param string $source    sciezka do zrodla
 * @param string $dest      sciezka przeznaczenia
 * @return void
 */
function copyRecursive($source, $dest) {
    if (is_dir($source)) {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), 
            RecursiveIteratorIterator::SELF_FIRST
        );

        foreach ($iterator as $file) {
            if ($file->isDir()) {
                mkdir($dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
            } else {
                copy($file, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
            }
        }
    } else {
        copy($source, $dest);
    }
}

Rozmiar pliku lub katalogu

/**
 * Rozmiar pliku lub katalogu
 *
 * UWAGA: Na systemach 32bitowych mozemy otrzymac niespodziewane wyniki dla plikow majacych rozmiar ponad 2GB
 *
 * @param string $path sciezka do pliku lub katalogu
 * @return int rozmiaru (w bajtach)
 */
function sizeRecursive($path) {
    $size = 0;
    if (is_dir($path)) {
        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));

        foreach ($iterator as $file) {
            $size += $file->getSize();
        }
    } else {
        $size = filesize($path);
    }

    return $size;
}

Funkcja tail (*unix) – odczytanie kilku (ostatnich) linii z pliku

/**
 * Funkcja tail (*unix) - odczytanie kilku (ostatnich) linii z pliku
 *
 * UWAGA: Ponizsza funkcja dziala dla plikow majacych zakonczenie linii rowne CRLF, LF lub CR
 *
 * @param string $file  sciezka do pliku
 * @param int $lines    ilosc linii do zwrocenia przez funkcje
 * @return string
 */
function tail($file, $lines) {
    if ($lines < 1) {
        return '';
    }

    $line = '';
    $line_count = 0;
    $prev_char = '';
    $fp = fopen($file, 'r');
    $cursor = -1;

    fseek($fp, $cursor, SEEK_END);
    $char = fgetc($fp);

    while ($char !== false) {
        if ($char === "\\n" || $char === "\\r") {
            fseek($fp, --$cursor, SEEK_END);
            $next_char = fgetc($fp);

            if ($char === "\\n" && $next_char === "\\r") {
                $line_count++;
            } elseif ($char === "\\r" && $prev_char !== "\\n") {
                $line_count++;
            } elseif ($char === "\\n") {
                $line_count++;
            }

            fseek($fp, ++$cursor, SEEK_END);
        }

        if ($line_count == $lines) {
            break;
        }

        $line = $char . $line;
        $prev_char = $char;
        fseek($fp, --$cursor, SEEK_END);
        $char = fgetc($fp);
    }

    fclose($fp);

    return $line;
}

Upload archiwum ZIP i rozpakowanie na serwerze – PHP

archive_zipW ostatnim czasie, na potrzeby prac nad prywatnych projektem musiałem napisać sobie prostą klasę, która obsługiwałaby upload archiwum ZIP oraz jego rozpakowanie po stronie serwera w języku PHP. W tym celu wykorzystałem standardowy formularz HTML, który wysyła plik po uprzednim wybraniu go przez użytkownika. W klasie ZipUpload wykorzystuję również standardową klasę PHP jaką jest ZipArchive, która pozwala manipulować archiwami ZIP i która finalnie posłużyła mi do realizacji zamierzonego celu. Całość jest bardzo prosta i przejrzysta (mam nadzieję).

<?php

class ZipUpload {

    const DEFAULT_FILE_NAME = 'zip_file';
    const DEFAULT_EXTRACT_DIR = './extracted/';

    private $result = false;
    private $message = '';
    private $acceptedTypes = array(
        'application/zip', 
        'application/x-zip-compressed', 
        'multipart/x-zip', 
        'application/x-compressed',
    );

    public function getResult() {
        return $this->result;
    }

    public function getMessage() {
        return $this->message;
    }

    public function uploadAndExtractFile($formField = self::DEFAULT_FILE_NAME) {
        if (!empty($_FILES[$formField]['name'])) {
            $fileName = $_FILES[$formField]['name'];
            $source = $_FILES[$formField]['tmp_name'];
            $type = $_FILES[$formField]['type'];

            $name = explode('.', $fileName);
            $continue = strtolower($name[1]) == 'zip' ? true : false;
            if ($this->checkFileType($type) || $continue) {
                $this->extract($fileName, $source);
            } else {
                $this->message = 'The file you are trying to upload is not a .zip file. Please try again.';
            }
        } else {
            $this->message = 'File field is empty!';
        }
    }

    private function extract($fileName, $source, $to = self::DEFAULT_EXTRACT_DIR) {
        try {
            $this->checkIfDirExists($to);

            $targetPath = $to.$fileName;
            if (move_uploaded_file($source, $targetPath)) {
                $zip = new ZipArchive();
                $x = $zip->open($targetPath);
                if ($x === true) {
                    $zip->extractTo($to);
                    $zip->close();

                    unlink($targetPath);
                }
                $this->result = true;
                $this->message = 'Your .zip file was uploaded and unpacked.';
            } else {	
                $this->message = 'There was a problem with the upload. Please try again.';
            }
        } catch (Exception $e) {
            $this->message = $e->getMessage();
        }
    }

    private function checkFileType($type) {
        return in_array($type, $this->acceptedTypes);
    }

    private function checkIfDirExists($dirToExtract) {
        if (!is_dir($dirToExtract)) {
            if (!mkdir($dirToExtract, 0777, true)) {
                throw new \Exception('Can not create directory to extract ZIP archive.');
            }
        }
    }

}
<?php

// sprawdzamy czy formularz zostal wyslany
if (!empty($_POST)) {
    require_once ('./ZipUpload.php');

    // tworzymy nowy obiekt klasy ZipUpload
    $zipUpload = new ZipUpload();
    $zipUpload->uploadAndExtractFile();
    $result = $zipUpload->getResult();
    $message = $zipUpload->getMessage();
}

?>
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <title>Upload archiwum ZIP i rozpakowanie na serwerze - PHP</title>
    </head>

    <body>
        <?php if (!empty($message)) echo '<p class="'.($result ? 'success' : 'error').'">'.$message.'</p>'; ?>
        <form enctype="multipart/form-data" method="post" action="">
            <label>Wybierz archiwum ZIP: <input type="file" name="zip_file" /></label>
            <br />
            <input type="submit" name="submit" value="Wyślij" />
        </form>
    </body>
</html>