各位朋友,大家好!今天咱们聊聊WordPress里一个“简单粗暴”的家伙:WP_Filesystem_Direct
。 别被它的名字唬住,其实它就是个“直肠子”,直接用PHP的内置函数跟服务器的文件系统“硬碰硬”。咱们一起扒一扒它的源码,看看它是怎么干活的。
开场白:为啥要有 WP_Filesystem
?
在深入 WP_Filesystem_Direct
之前,先简单说说 WP_Filesystem
的作用。想象一下,WordPress要安装插件、更新主题、修改配置文件,都需要操作服务器上的文件。但不是每个人都有权限直接操作服务器。 有些服务器可能限制了PHP的执行权限,或者使用了FTP、SSH等方式来管理文件。
WP_Filesystem
就是一个抽象层,它把各种文件操作方式封装起来,让WordPress可以统一地操作文件,而不用关心底层到底是用哪种方式。 就像你用遥控器控制电视,不用管电视内部是用什么电路工作的。
WP_Filesystem_Direct
就是 WP_Filesystem
的一个实现类,也是最简单的一种实现。它假设你的PHP脚本有足够的权限直接操作文件系统,所以它直接调用PHP的内置函数来完成文件操作。
WP_Filesystem_Direct
类:源码剖析
咱们直接看代码,边看边聊:
<?php
class WP_Filesystem_Direct {
/**
* Constructor.
*
* @since 2.5.0
*
* @param mixed $arg Ignored.
*/
public function __construct( $arg = '' ) {}
/**
* Connect to the Filesystem.
*
* @since 2.5.0
*
* @return true Always returns true.
*/
public function connect() {
return true;
}
/**
* Get the last error that occurred.
*
* @since 2.5.0
*
* @return false Always returns false.
*/
public function errors() {
return false;
}
/**
* Determine if the connection succeeded.
*
* @since 2.5.0
*
* @return true Always returns true.
*/
public function connected() {
return true;
}
/**
* Read a file.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false The file contents on success, false on failure.
*/
public function get_contents( $file ) {
return @file_get_contents( $file );
}
/**
* Write a string to a file.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @param string $contents The file contents.
* @param int $mode Optional. Permissions for the new file. Default false.
* @return bool True on success, false on failure.
*/
public function put_contents( $file, $contents, $mode = false ) {
if ( ! $mode ) {
return @file_put_contents( $file, $contents );
} else {
return @file_put_contents( $file, $contents, $mode );
}
}
/**
* Read a file into an array.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return array|false The file contents as an array, false on failure.
*/
public function get_contents_array( $file ) {
return @file( $file );
}
/**
* Get the current file permissions.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false Permissions of the file, false on failure.
*/
public function get_chmod( $file ) {
return @substr( decoct( fileperms( $file ) ), -3 );
}
/**
* Change the file permissions.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @param int $chmod Permissions as octal number.
* @return bool True on success, false on failure.
*/
public function chmod( $file, $chmod = false ) {
if ( ! $chmod ) {
if ( $this->is_writable( $file ) ) {
return true;
} else {
return false;
}
}
return @chmod( $file, octdec( $chmod ) );
}
/**
* Get the file owner.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false The owner of the file, false on failure.
*/
public function owner( $file ) {
if ( ! function_exists( 'posix_getpwuid' ) ) {
return false;
}
$owner_id = @fileowner( $file );
if ( ! $owner_id ) {
return false;
}
$owner_array = @posix_getpwuid( $owner_id );
if ( ! $owner_array ) {
return false;
}
return $owner_array['name'];
}
/**
* Get the file group.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return string|false The group of the file, false on failure.
*/
public function group( $file ) {
if ( ! function_exists( 'posix_getgrgid' ) ) {
return false;
}
$group_id = @filegroup( $file );
if ( ! $group_id ) {
return false;
}
$group_array = @posix_getgrgid( $group_id );
if ( ! $group_array ) {
return false;
}
return $group_array['name'];
}
/**
* Determine if a file is writable.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return bool True if the file is writable, false otherwise.
*/
public function is_writable( $file ) {
return @is_writable( $file );
}
/**
* Determine if a file exists.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return bool True if the file exists, false otherwise.
*/
public function exists( $file ) {
return @file_exists( $file );
}
/**
* Determine if a file is a file.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return bool True if the file is a file, false otherwise.
*/
public function is_file( $file ) {
return @is_file( $file );
}
/**
* Determine if a directory is a directory.
*
* @since 2.5.0
*
* @param string $path Path to the directory.
* @return bool True if the path is a directory, false otherwise.
*/
public function is_dir( $path ) {
return @is_dir( $path );
}
/**
* Determine if a file is a symlink.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return bool True if the file is a symlink, false otherwise.
*/
public function is_link( $file ) {
return @is_link( $file );
}
/**
* Is a file or directory empty?
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return bool True if the file/folder is empty, false otherwise.
*/
public function is_empty( $file ) {
if ( ! $this->exists( $file ) ) {
return true;
}
if ( $this->is_file( $file ) ) {
return ( 0 == @filesize( $file ) );
} elseif ( is_readable( $file ) ) {
$filelist = @scandir( $file );
if ( ! $filelist || 2 == count( $filelist ) ) {
return true;
}
}
return false;
}
/**
* Create a directory.
*
* @since 2.5.0
*
* @param string $path Path to create.
* @param int $chmod Optional. Permissions for the new directory. Default false.
* @param bool $recursive Optional. Whether to create parent directories. Default false.
* @return bool True on success, false on failure.
*/
public function mkdir( $path, $chmod = false, $recursive = false ) {
if ( ! $chmod ) {
$chmod = FS_CHMOD_DIR;
}
if ( $recursive ) {
return @mkdir( $path, octdec( $chmod ), true );
} else {
return @mkdir( $path, octdec( $chmod ) );
}
}
/**
* Delete a directory.
*
* @since 2.5.0
*
* @param string $path Path to delete.
* @param bool $recursive Optional. Whether to delete recursively. Default false.
* @return bool True on success, false on failure.
*/
public function rmdir( $path, $recursive = false ) {
if ( $recursive ) {
return $this->delete( $path, true );
} else {
return @rmdir( $path );
}
}
/**
* Upload a file.
*
* @since 2.5.0
*
* @param string $file Path to the source file.
* @param string $destination Path to the destination file.
* @return bool True on success, false on failure.
*/
public function upload( $file, $destination ) {
return @copy( $file, $destination );
}
/**
* Copy a file.
*
* @since 2.5.0
*
* @param string $source Path to the source file.
* @param string $destination Path to the destination file.
* @param bool $overwrite Optional. Whether to overwrite the destination file. Default false.
* @param int $mode Optional. Permissions for the new file. Default false.
* @return bool True on success, false on failure.
*/
public function copy( $source, $destination, $overwrite = false, $mode = false ) {
if ( ! $overwrite && $this->exists( $destination ) ) {
return false;
}
if ( ! $mode ) {
return @copy( $source, $destination );
} else {
//PHP < 5.3 doesn't support copy with context. Use workaround
$content = $this->get_contents( $source );
if ( false === $content ) {
return false;
}
return $this->put_contents( $destination, $content, $mode );
}
}
/**
* Move a file.
*
* @since 2.5.0
*
* @param string $source Path to the source file.
* @param string $destination Path to the destination file.
* @param bool $overwrite Optional. Whether to overwrite the destination file. Default false.
* @return bool True on success, false on failure.
*/
public function move( $source, $destination, $overwrite = false ) {
if ( ! $overwrite && $this->exists( $destination ) ) {
return false;
}
return @rename( $source, $destination );
}
/**
* Delete a file or directory.
*
* @since 2.5.0
*
* @param string $path Path to delete.
* @param bool $recursive Optional. Whether to delete recursively. Default false.
* @param string $type Optional. What type of resource to delete. Default false.
* 'f' for file, 'd' for directory.
* @return bool True on success, false on failure.
*/
public function delete( $path, $recursive = false, $type = false ) {
if ( empty( $path ) ) {
return false;
}
if ( 'f' == $type || $this->is_file( $path ) ) {
return @unlink( $path );
}
if ( ! $recursive && $this->is_dir( $path ) ) {
return @rmdir( $path );
}
if ( ! $this->is_dir( $path ) ) {
return false;
}
// Use glob() to support wildcards
$files = glob( trailingslashit( $path ) . '*' );
if ( ! empty( $files ) ) {
foreach ( $files as $file ) {
if ( is_dir( $file ) ) {
$this->delete( $file, true );
} else {
@unlink( $file );
}
}
}
return @rmdir( $path );
}
/**
* Get the file modification time.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return int|false The file modification time, false on failure.
*/
public function mtime( $file ) {
return @filemtime( $file );
}
/**
* Get the file size.
*
* @since 2.5.0
*
* @param string $file Path to the file.
* @return int|false The file size, false on failure.
*/
public function size( $file ) {
return @filesize( $file );
}
/**
* Get the file system root path.
*
* @since 2.5.0
*
* @param string $file Optional. Path to a file.
* @return string The file system root path.
*/
public function dirlist( $path, $include_hidden = false, $recursive = false ) {
if ( ! $this->is_dir( $path ) ) {
return false;
}
$inner = array();
$dir = @opendir( $path );
if ( $dir ) {
while ( false !== ( $entry = readdir( $dir ) ) ) {
if ( '.' == $entry || '..' == $entry ) {
continue;
}
if ( ! $include_hidden && '.' == $entry[0] ) {
continue;
}
$fullpath = trailingslashit( $path ) . $entry;
if ( $this->is_dir( $fullpath ) ) {
$type = 'd';
if ( $recursive ) {
$inner[ $entry ] = $this->dirlist( $fullpath, $include_hidden, $recursive );
} else {
$inner[ $entry ] = array( 'name' => $entry, 'type' => 'd' );
}
} else {
$type = 'f';
$inner[ $entry ] = array( 'name' => $entry, 'type' => 'f' );
}
if ( 'd' == $type ) {
if ( ! isset( $inner[ $entry ]['size'] ) ) {
$inner[ $entry ]['size'] = $this->size( $fullpath );
}
} else {
$inner[ $entry ]['size'] = $this->size( $fullpath );
}
$inner[ $entry ]['perms'] = $this->get_chmod( $fullpath );
$inner[ $entry ]['owner'] = $this->owner( $fullpath );
$inner[ $entry ]['group'] = $this->group( $fullpath );
$inner[ $entry ]['lastmodunix'] = $this->mtime( $fullpath );
$inner[ $entry ]['lastmod'] = date( 'F d, Y g:i:s A', $inner[ $entry ]['lastmodunix'] );
}
@closedir( $dir );
}
return $inner;
}
}
重点方法解读
咱们挑几个有代表性的方法,仔细看看:
-
get_contents( $file )
和put_contents( $file, $contents, $mode = false )
:这两个方法分别负责读取和写入文件内容。它们直接使用了PHP的
file_get_contents()
和file_put_contents()
函数。public function get_contents( $file ) { return @file_get_contents( $file ); } public function put_contents( $file, $contents, $mode = false ) { if ( ! $mode ) { return @file_put_contents( $file, $contents ); } else { return @file_put_contents( $file, $contents, $mode ); } }
注意
@
符号: 这个符号的作用是抑制错误显示。如果file_get_contents()
或file_put_contents()
出错(比如文件不存在、权限不足),PHP会抛出一个错误。@
符号可以阻止这个错误显示出来,让代码更健壮一些。 虽然抑制错误有时候不是好习惯,但在这里,由于WP_Filesystem
是一个抽象层,它需要自己处理错误,而不是让PHP直接抛出错误。 -
mkdir( $path, $chmod = false, $recursive = false )
:这个方法用于创建目录。它使用了PHP的
mkdir()
函数。public function mkdir( $path, $chmod = false, $recursive = false ) { if ( ! $chmod ) { $chmod = FS_CHMOD_DIR; //FS_CHMOD_DIR 通常定义在 wp-config.php 或者其它配置文件中,表示默认的目录权限。 } if ( $recursive ) { return @mkdir( $path, octdec( $chmod ), true ); } else { return @mkdir( $path, octdec( $chmod ) ); } }
$chmod
: 指定了新目录的权限。如果未指定,则使用FS_CHMOD_DIR
常量作为默认权限。$recursive
: 指定是否递归创建目录。如果为true
,则会创建所有不存在的父目录。octdec( $chmod )
: 将八进制权限转换为十进制。mkdir()
函数需要十进制的权限值。
例子:
$fs = new WP_Filesystem_Direct(); $path = '/path/to/new/directory'; $chmod = '0755'; //八进制权限 $recursive = true; $result = $fs->mkdir( $path, $chmod, $recursive ); if ( $result ) { echo '目录创建成功!'; } else { echo '目录创建失败!'; }
-
delete( $path, $recursive = false, $type = false )
:这个方法用于删除文件或目录。它使用了PHP的
unlink()
和rmdir()
函数。public function delete( $path, $recursive = false, $type = false ) { if ( empty( $path ) ) { return false; } if ( 'f' == $type || $this->is_file( $path ) ) { return @unlink( $path ); } if ( ! $recursive && $this->is_dir( $path ) ) { return @rmdir( $path ); } if ( ! $this->is_dir( $path ) ) { return false; } // Use glob() to support wildcards $files = glob( trailingslashit( $path ) . '*' ); if ( ! empty( $files ) ) { foreach ( $files as $file ) { if ( is_dir( $file ) ) { $this->delete( $file, true ); } else { @unlink( $file ); } } } return @rmdir( $path ); }
$recursive
: 指定是否递归删除目录。如果为true
,则会删除目录中的所有文件和子目录。$type
: 指定要删除的资源类型。'f'
表示文件,'d'
表示目录。如果未指定,则根据路径判断资源类型。
例子:
$fs = new WP_Filesystem_Direct(); $path = '/path/to/file.txt'; $result = $fs->delete( $path ); if ( $result ) { echo '文件删除成功!'; } else { echo '文件删除失败!'; } $path = '/path/to/directory'; $recursive = true; $result = $fs->delete( $path, $recursive ); if ( $result ) { echo '目录删除成功!'; } else { echo '目录删除失败!'; }
-
dirlist( $path, $include_hidden = false, $recursive = false )
:这个方法用于列出目录中的文件和子目录。 它使用了PHP的
opendir()
,readdir()
,closedir()
以及scandir()
函数, 并且递归调用自身来处理子目录。public function dirlist( $path, $include_hidden = false, $recursive = false ) { if ( ! $this->is_dir( $path ) ) { return false; } $inner = array(); $dir = @opendir( $path ); if ( $dir ) { while ( false !== ( $entry = readdir( $dir ) ) ) { if ( '.' == $entry || '..' == $entry ) { continue; } if ( ! $include_hidden && '.' == $entry[0] ) { continue; } $fullpath = trailingslashit( $path ) . $entry; if ( $this->is_dir( $fullpath ) ) { $type = 'd'; if ( $recursive ) { $inner[ $entry ] = $this->dirlist( $fullpath, $include_hidden, $recursive ); } else { $inner[ $entry ] = array( 'name' => $entry, 'type' => 'd' ); } } else { $type = 'f'; $inner[ $entry ] = array( 'name' => $entry, 'type' => 'f' ); } if ( 'd' == $type ) { if ( ! isset( $inner[ $entry ]['size'] ) ) { $inner[ $entry ]['size'] = $this->size( $fullpath ); } } else { $inner[ $entry ]['size'] = $this->size( $fullpath ); } $inner[ $entry ]['perms'] = $this->get_chmod( $fullpath ); $inner[ $entry ]['owner'] = $this->owner( $fullpath ); $inner[ $entry ]['group'] = $this->group( $fullpath ); $inner[ $entry ]['lastmodunix'] = $this->mtime( $fullpath ); $inner[ $entry ]['lastmod'] = date( 'F d, Y g:i:s A', $inner[ $entry ]['lastmodunix'] ); } @closedir( $dir ); } return $inner; }
$include_hidden
: 指定是否包含隐藏文件。$recursive
: 指定是否递归列出子目录。
返回结果:
dirlist()
方法返回一个多维数组,包含了目录中的文件和子目录的信息。 数组的结构大致如下:array( 'file1.txt' => array( 'name' => 'file1.txt', 'type' => 'f', 'size' => 1024, 'perms' => '755', 'owner' => 'www-data', 'group' => 'www-data', 'lastmodunix' => 1678886400, 'lastmod' => 'March 15, 2023 12:00:00 AM' ), 'dir1' => array( 'name' => 'dir1', 'type' => 'd', 'size' => 4096, 'perms' => '755', 'owner' => 'www-data', 'group' => 'www-data', 'lastmodunix' => 1678886400, 'lastmod' => 'March 15, 2023 12:00:00 AM', 'file2.txt' => array( // 如果 recursive 为 true,这里会包含子目录的内容 'name' => 'file2.txt', 'type' => 'f', 'size' => 2048, 'perms' => '755', 'owner' => 'www-data', 'group' => 'www-data', 'lastmodunix' => 1678886400, 'lastmod' => 'March 15, 2023 12:00:00 AM' ) ) )
例子:
$fs = new WP_Filesystem_Direct(); $path = '/path/to/directory'; $result = $fs->dirlist( $path, false, true ); if ( $result ) { echo '<pre>'; print_r( $result ); echo '</pre>'; } else { echo '无法列出目录!'; }
WP_Filesystem_Direct
的优缺点
优点 | 缺点 |
---|---|
速度快: 直接调用PHP内置函数,没有额外的开销。 | 安全性: 需要PHP有足够的权限操作文件系统,这可能会带来安全风险。 如果服务器配置不当,可能会导致恶意用户通过PHP脚本修改或删除重要文件。 |
简单易用: 代码简单,容易理解和维护。 | 适用性有限: 只能在PHP有足够权限操作文件系统的服务器上使用。在一些安全限制较多的服务器上,可能需要使用其他的 WP_Filesystem 实现类,比如 WP_Filesystem_FTP 。 |
无需额外配置: 不需要安装额外的扩展或配置。 | 错误处理: 依赖于PHP的错误处理机制,可能不够灵活。虽然使用了 @ 符号来抑制错误显示,但仍然需要额外的代码来处理错误。 |
直接操作: 可以直接操作文件系统,对于一些需要底层操作的场景很有用。 | 权限依赖: 它的成功运行完全依赖于PHP进程的用户权限。 如果PHP进程的用户没有足够的权限来访问或修改文件,WP_Filesystem_Direct 将无法正常工作。 这意味着它可能不适用于所有环境,特别是在共享主机或安全性要求较高的服务器上。 |
资源消耗少: 由于直接使用PHP内置函数,资源消耗相对较少。 | 缺乏抽象: 虽然 WP_Filesystem 提供了抽象层,但 WP_Filesystem_Direct 本身并没有对文件操作进行额外的抽象或封装。 这意味着代码与底层文件系统操作紧密耦合,如果需要更换文件操作方式(例如,使用不同的文件系统或协议),则需要修改大量代码。 |
内置支持: WP_Filesystem_Direct 是 WordPress 核心的一部分,无需额外安装或引入。 |
缺乏高级功能: WP_Filesystem_Direct 主要提供基本的文件操作功能,例如读取、写入、创建、删除等。 它缺乏一些高级功能,例如文件压缩、解压缩、加密、解密等。 如果需要这些高级功能,可能需要使用其他的 WP_Filesystem 实现类或第三方库。 |
使用场景
- 本地开发环境: 在本地开发环境中,通常PHP有足够的权限操作文件系统,可以使用
WP_Filesystem_Direct
。 - 一些权限宽松的服务器: 在一些权限宽松的服务器上,如果确定PHP有足够的权限,可以使用
WP_Filesystem_Direct
。 - 对性能要求高的场景: 如果对文件操作的性能要求很高,可以使用
WP_Filesystem_Direct
,因为它没有额外的开销。
总结
WP_Filesystem_Direct
是 WP_Filesystem
的一个简单而直接的实现类。 它直接使用PHP的内置函数来操作文件系统,速度快,易于使用,但安全性较低,适用性有限。 在选择使用 WP_Filesystem_Direct
时,需要权衡其优缺点,并根据实际情况选择合适的 WP_Filesystem
实现类。
希望今天的讲解对你有所帮助! 谢谢大家!