mkdir的前世今生

前几日有同事询问我Yii2中是否有封装递归创建目录的方法,我告知了他CFileHelper的用法,对这个事情我从未有过怀疑,任何成熟的框架必然会封装各种文件操作的方法,但过后我寻思良久,mkdir明明有$recursive参数,为何所有的框架都要封装创建文件的方法呢?

用过PHP4的人可能还记得,PHP4中的mkdir定义是这样的:

1
bool mkdir ( string $pathname [,int $mode = 0777 ] )

而PHP5中的定义如下:

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

这两个参数最早出现在2003年的php源码中,并在php5.0.0中发布,但是一直到PHP4的最后一个版本4.4.9这两个参数也没有被添加。

那么我们有了第一个答案,使用FileHelper是有兼容性方面考虑的,如果你的程序有可能在低版本的PHP中运行,需要使用外部的文件创建方法。

那么下一个问题,众所周知,Yii对PHP的版本要求是5.1.0,Yii2的版本要求是5.4.0,大多新版框架和系统都已经放弃了对PHP4的支持,那么在高版本PHP上运行的为什么要用FileHelper?

第二个答案是这样的,如果你仅仅是用来创建文件夹,那么mkdir的确更方便,但是mkdir有着几个猪一样的队友,比如rmdir和copy,他们都不支持递归,所以FileHelper更方便,他提供了内置函数外的各种强大功能,还能兼顾兼容性。

Yii2\helpers\CFileHelper 定义如下:

1
2
3
4
5
6
7
8
9
10
public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
public static function localize($file, $language = null, $sourceLanguage = null)
public static function getMimeType($file, $magicFile = null, $checkExtension = true)
public static function getMimeTypeByExtension($file, $magicFile = null)
public static function getExtensionsByMimeType($mimeType, $magicFile = null)
public static function copyDirectory($src, $dst, $options = [])
public static function removeDirectory($dir, $options = [])
public static function findFiles($dir, $options = [])
public static function filterPath($path, $options)
public static function createDirectory($path, $mode = 0775, $recursive = true)

什么?没看出来强大?好,请看copyDirectory方法关于$option参数的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
* @param array $options options for directory copy. Valid options are:
*
* - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775.
* - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting.
* - filter: callback, a PHP callback that is called for each directory or file.
* The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
* The callback can return one of the following values:
*
* * true: the directory or file will be copied (the "only" and "except" options will be ignored)
* * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored)
* * null: the "only" and "except" options will determine whether the directory or file should be copied
*
* - only: array, list of patterns that the file paths should match if they want to be copied.
* A path matches a pattern if it contains the pattern string at its end.
* For example, '.php' matches all file paths ending with '.php'.
* Note, the '/' characters in a pattern matches both '/' and '\' in the paths.
* If a file path matches a pattern in both "only" and "except", it will NOT be copied.
* - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied.
* A path matches a pattern if it contains the pattern string at its end.
* Patterns ending with '/' apply to directory paths only, and patterns not ending with '/'
* apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b';
* and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches
* both '/' and '\' in the paths.
* - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true.
* - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
* - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
* If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file to be copied from, while `$to` is the copy target.
* - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
* The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
* file copied from, while `$to` is the copy target.

@bobhero 通过另一角度分析了这个事情,他认为mkdir的这些队友如此设计是有原因的,但最终我没有找到答案。因为Python3关于文件操作的方法是这样的:

1
2
3
4
5
6
7
os.mkdir(path, mode=0o777, *, dir_fd=None)
os.mkdirs(name, mode=0o777, exist_ok=False)
os.unlink(path, *, dir_fd=None)
os.remove(path, *, dir_fd=None)
os.removedirs(name)
os.rmdir(path, *, dir_fd=None)
shutil.rmtree(path[, ignore_errors[, onerror]])

而Ruby2.2.2的文件操作是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Dir.mkdir(string [, integer])
Dir.rmdir(string)
Dir.unlink(string)
Dir.delete(string)
FileUtils.cd(dir, options)
FileUtils.cd(dir, options) {|dir| .... }
FileUtils.pwd()
FileUtils.mkdir(dir, options)
FileUtils.mkdir(list, options)
FileUtils.mkdir_p(dir, options)
FileUtils.mkdir_p(list, options)
FileUtils.rmdir(dir, options)
FileUtils.rmdir(list, options)
FileUtils.ln(old, new, options)
FileUtils.ln(list, destdir, options)
FileUtils.ln_s(old, new, options)
FileUtils.ln_s(list, destdir, options)
FileUtils.ln_sf(src, dest, options)
FileUtils.cp(src, dest, options)
FileUtils.cp(list, dir, options)
FileUtils.cp_r(src, dest, options)
FileUtils.cp_r(list, dir, options)
FileUtils.mv(src, dest, options)
FileUtils.mv(list, dir, options)
FileUtils.rm(list, options)
FileUtils.rm_r(list, options)
FileUtils.rm_rf(list, options)
FileUtils.install(src, dest, mode = <src's>, options)
FileUtils.chmod(mode, list, options)
FileUtils.chmod_R(mode, list, options)
FileUtils.chown(user, group, list, options)
FileUtils.chown_R(user, group, list, options)
FileUtils.touch(list, options)

出于什么样的考虑每个语言对此设计有这样的差异我们不得而知,但这的确引发了我不少的思考,你呢?

注:文中提到的FileHelper不仅仅指Yii的CFileHelper,泛指所有第三方文件操作库。