luyadev/yii-helpers

View on GitHub
src/helpers/FileHelper.php

Summary

Maintainability
A
1 hr
Test Coverage
B
87%
<?php

namespace luya\yii\helpers;

use Exception;
use Yii;

/**
 * Helper methods when dealing with Files.
 *
 * Extends the {{yii\helpers\FileHelper}} class by some useful functions like:
 *
 * + {{luya\yii\helpers\FileHelper::humanReadableFilesize()}}
 * + {{luya\yii\helpers\FileHelper::ensureExtension()}}
 * + {{luya\yii\helpers\FileHelper::md5sum()}}
 * + {{luya\yii\helpers\FileHelper::writeFile()}}
 * + {{luya\yii\helpers\FileHelper::getFileContent()}}
 * + {{luya\yii\helpers\FileHelper::unlink()}}
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class FileHelper extends \yii\helpers\BaseFileHelper
{
    /**
     * Generate a human readable size informations from provided Byte/s size.
     *
     * @param integer $size The size to convert in Byte
     * @return string The readable size definition
     */
    public static function humanReadableFilesize($size)
    {
        $mod = 1024;
        $units = explode(' ', 'B KB MB GB TB PB');
        for ($i = 0; $size > $mod; ++$i) {
            $size /= $mod;
        }

        return round($size, 2).' '.$units[$i];
    }

    /**
     * Append a file extension to a path/file if there is no or an empty extension provided, this
     * helper methods is used to make sure the right extension existing on files.
     *
     * @param string $file The file where extension should be append if not existing
     * @param string $extension
     * @return string the ensured file/path with extension
     */
    public static function ensureExtension($file, $extension)
    {
        $info = pathinfo($file);
        if (!isset($info['extension']) || empty($info['extension'])) {
            $file = rtrim($file, '.') . '.' . $extension;
        }

        return $file;
    }

    /**
     * Provide class informations from a file path or file content.
     *
     * This is used when working with file paths from Composer, in order to detect class and namespace from a given file.
     *
     * @param string $file The file path to the class into order to get infos from, could also be the content directly from a given file.
     * @return array If the given file path is a file, it will return an array with the keys:
     * + `namespace`: the namespace of the file, `false` if no namespace could have been determined.
     * + `class`: the class name of the file, `false` if no class could have been determined.
     */
    public static function classInfo($file)
    {
        if (is_file($file)) {
            $phpCode = file_get_contents($file);
        } else {
            $phpCode = $file;
        }

        $namespace = false;

        if (preg_match('/^namespace\s+(.+?);(\s+|\r\n)?$/sm', $phpCode, $results)) {
            $namespace = $results[1];
        }

        $classes = self::classNameByTokens($phpCode);

        return ['namespace' => $namespace, 'class' => end($classes)];
    }

    /**
     * Tokenize the PHP code from a given class in in order to determine the class name.
     *
     * @param string $phpCode The PHP code to tokenize and find the class name from
     * @return array
     */
    private static function classNameByTokens($phpCode)
    {
        $classes = [];
        $tokens = token_get_all($phpCode);
        $count = count($tokens);
        for ($i = 2; $i < $count; $i++) {
            if ($tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE && $tokens[$i][0] == T_STRING) {
                $classes[] = $tokens[$i][1];
            }
        }

        return $classes;
    }

    /**
     * Create a unique hash name from a given file.
     *
     * Warning
     * Because PHP's integer type is signed many `crc32()` checksums will result in negative integers on 32-bit platforms. On 64-bit installations all `crc32()` results will be positive integers though.
     * So you need to use the `%u` formatter of `sprintf()` or `printf()` to get the string representation of the unsigned `crc32()` checksum in decimal format.
     *
     * @param string $fileName The file name which should be hashed.
     * @return string
     */
    public static function hashName($fileName)
    {
        return sprintf('%s', hash('crc32b', uniqid($fileName, true)));
    }

    /**
     * Get extension and name from a file for the provided source/path of the file.
     *
     * @param string $sourceFile The path of the file
     * @return object With extension and name keys.
     */
    public static function getFileInfo($sourceFile)
    {
        // pathinfo() always returns an array event the path does not exists
        $path = pathinfo($sourceFile);

        return (object) [
            'extension' => !empty($path['extension']) ? mb_strtolower($path['extension'], 'UTF-8') : false,
            'name' => !empty($path['filename']) ? $path['filename'] : false,
            'source' => $sourceFile,
            'sourceFilename' => !empty($path['filename']) ? $path['dirname'] . DIRECTORY_SEPARATOR . $path['filename'] : false,
        ];
    }

    /**
     * Generate a MD5 hash of a file. This is equal to `md5sum` command.
     *
     * @param string $sourceFile The path to the file
     * @return false|string Returns false or the MD5 hash of this file
     */
    public static function md5sum($sourceFile)
    {
        return file_exists($sourceFile) ? hash_file('md5', $sourceFile) : false;
    }

    /**
     * Basic helper method to write files with exception capture. The filename will auto-wrapped
     * through the `Yii::getAlias()` function.
     *
     * @param string $fileName The path to the file with filename
     * @param string $content The content to store in this file
     * @return boolean
     */
    public static function writeFile($fileName, $content)
    {
        try {
            $response = file_put_contents(Yii::getAlias($fileName), $content);
            if ($response === false) {
                return false;
            }
        } catch (Exception $error) {
            return false;
        }

        return true;
    }

    /**
     * Basic helper to retrieve the content of a file and catched exception. The filename
     * will auto-alias encoded by `Yii::getAlias()` function.
     *
     * @param string $fileName The path to the file to get the content
     * @return string|boolean
     */
    public static function getFileContent($fileName)
    {
        try {
            return file_get_contents(Yii::getAlias($fileName));
        } catch (Exception $error) {
            return false;
        }
    }

    /**
     * Unlink a file, which handles symlinks.
     *
     * @param string $file The file path to the file to delete.
     * @return boolean Whether the file has been removed or not.
     */
    public static function unlink($file)
    {
        // no errors should be thrown, return false instead.
        try {
            if (parent::unlink($file)) {
                return true;
            }
        } catch (\Exception $e) {
        }

        // try to force symlinks
        if (is_link($file)) {
            $sym = @readlink($file);
            if ($sym) {
                if (@unlink($file)) {
                    return true;
                }
            }
        }

        // try to use realpath()
        if (realpath($file) && realpath($file) !== $file) {
            if (@unlink(realpath($file))) {
                return true;
            }
        }

        return false;
    }
}