|
| 1 | +# Advanced Usage of WP File System |
| 2 | + |
| 3 | +This documentation is intended for developers who want to gain a deeper understanding of how the WP File System library |
| 4 | +works and use its advanced features such as hooks, protected mode, and manual service instance creation. |
| 5 | + |
| 6 | +## WordPress File System Basics and Access Methods |
| 7 | + |
| 8 | +To understand the full power of the WPFS library, it's important to know what problem it solves at the lowest level. |
| 9 | +WordPress uses an abstraction for file access — WP_Filesystem — to ensure security and compatibility with various |
| 10 | +hosting environments. This abstraction can work in several modes (methods). |
| 11 | + |
| 12 | +| Method | Description | When to Use | |
| 13 | +|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------| |
| 14 | +| `direct` | The simplest and fastest method. File operations are performed directly using PHP functions (fopen, fwrite, etc.) under the web server user account (e.g., www-data). | Used by default on properly configured servers where the web server has write permissions to the required WordPress directories. | |
| 15 | +| `ssh2` | Operations are performed through a secure SSH connection to the server using provided credentials. | Used on servers where the web server doesn't have direct write permissions but has SSH access. Requires the PHP ssh2 extension. | |
| 16 | +| `ftpext` | Operations are performed via FTP using credentials of an FTP user who has write permissions. | Common method on virtual hosting. Requires the PHP ftp extension. | |
| 17 | +| `ftpsockets` | Alternative implementation of FTP client using pure PHP sockets. Used when the ftp extension is not available. | Fallback option for FTP that doesn't require additional PHP extensions. | |
| 18 | + |
| 19 | +### Problems and Their Solution with WPFS |
| 20 | + |
| 21 | +- Permission problem: If using `direct` method on a server where `www-data` doesn't have write permissions to the |
| 22 | + `wp-content/uploads` folder, any attempt to save a file will fail. |
| 23 | +- Credentials problem: Methods `ssh2` and `ftp*` require credentials. WordPress usually requests them from the user |
| 24 | + through a form in the admin panel. This makes it impossible to perform file operations in the background (e.g., via |
| 25 | + CRON) or through API. |
| 26 | + |
| 27 | +### How WPFS Solves This: |
| 28 | + |
| 29 | +1. Automatic initialization: WPFS automatically initializes WP_Filesystem, eliminating the need to do it manually. |
| 30 | +2. Using constants: WPFS relies on credentials being defined as constants in `wp-config.php` for non-interactive |
| 31 | + operations (CRON, WP-CLI). This is a standard WordPress practice. |
| 32 | + |
| 33 | +### Configuration in wp-config.php |
| 34 | + |
| 35 | +You can force the access method and specify the credentials in the `wp-config.php` file. |
| 36 | + |
| 37 | +```php |
| 38 | +// Force filesystem method |
| 39 | +// Possible values: 'direct', 'ssh2', 'ftpext', 'ftpsockets' |
| 40 | +define('FS_METHOD', 'direct'); |
| 41 | + |
| 42 | +// MAIN CREDENTIALS (for FTP, FTPS, SSH2) |
| 43 | +define('FTP_HOST', 'your-server.com'); |
| 44 | +define('FTP_USER', 'username'); |
| 45 | +define('FTP_PASS', 'password'); |
| 46 | +define('FTP_PORT', '22'); // Port 22 for SSH, 21 for FTP |
| 47 | + |
| 48 | +// CONNECTION SETTINGS |
| 49 | +define('FTP_SSL', false); // true for FTPS (FTP methods only) |
| 50 | + |
| 51 | +// SSH KEYS (alternative to password for SSH2) |
| 52 | +define('FTP_PUBKEY', '/home/user/.ssh/id_rsa.pub'); |
| 53 | +define('FTP_PRIKEY', '/home/user/.ssh/id_rsa'); |
| 54 | + |
| 55 | +// DEFAULT ACCESS PERMISSIONS |
| 56 | +define('FS_CHMOD_DIR', 0755); |
| 57 | +define('FS_CHMOD_FILE', 0644); |
| 58 | +``` |
| 59 | + |
| 60 | +## Decorator Providers: Hooks and Guardian |
| 61 | + |
| 62 | +By default, FSFactory creates basic service instances that simply call corresponding WordPress functions. |
| 63 | +However, you can globally enable two powerful decorators: FSHooksProvider and FSGuardiansProvider. |
| 64 | + |
| 65 | +### FSGuardiansProvider (Guardian) |
| 66 | + |
| 67 | +Purpose: Switches error handling mode. By default, according to WordPress API, most methods simply return `false` in |
| 68 | +case of failure. This can make debugging difficult. When "Guardian" is enabled, typed exceptions will be thrown instead |
| 69 | +of `false`. |
| 70 | + |
| 71 | +- `FSPathNotFoundException`: Resource (file or directory) not found. |
| 72 | +- `FSPermissionException`: Resource exists, but insufficient permissions for the operation. |
| 73 | +- `FSException`: General filesystem error. |
| 74 | + |
| 75 | +### Как использовать: |
| 76 | + |
| 77 | +```php |
| 78 | +use Metapraxis\WPFileSystem\Provider\FSGuardiansProvider; |
| 79 | +use Metapraxis\WPFileSystem\Facade\WPFS; |
| 80 | +use Metapraxis\WPFileSystem\Exceptions\FSPathNotFoundException; |
| 81 | + |
| 82 | +// Enabling exception mode |
| 83 | +FSGuardiansProvider::setStatus(true); |
| 84 | + |
| 85 | +try { |
| 86 | + $content = WPFS::getContents('/path/to/non-existent-file.txt'); |
| 87 | +} catch (FSPathNotFoundException $e) { |
| 88 | + wp_log_error($e->getMessage()); |
| 89 | +} finally { |
| 90 | + // You can disable it, that is, use it as a temporary effect, |
| 91 | + // or not disable it and thereby introduce a global state. |
| 92 | + FSGuardiansProvider::setStatus(false); |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### FSHooksProvider (Hooks) |
| 97 | + |
| 98 | +Purpose: Adds the ability to "hook into" file operations using standard WordPress hooks |
| 99 | +(`do_action` and `apply_filters`). This makes it easy to add logging, monitoring, or modify operation behavior |
| 100 | +on the fly. |
| 101 | + |
| 102 | +All hook names can be found in traits inside `src/Hooks/Collection/`. |
| 103 | + |
| 104 | +### How to use: |
| 105 | + |
| 106 | +```php |
| 107 | +use Metapraxis\WPFileSystem\Provider\FSHooksProvider; |
| 108 | +use Metapraxis\WPFileSystem\Facade\WPFS; |
| 109 | +use Metapraxis\WPFileSystem\Hooks\Collection\ActionHooks; |
| 110 | + |
| 111 | +// Enabling hooks |
| 112 | +FSHooksProvider::setStatus(true); |
| 113 | + |
| 114 | +// Adding an action that will be triggered AFTER writing to any file |
| 115 | +add_action(ActionHooks::$AFTER_PUT_CONTENTS_ACTION, function($result, $file, $contents) { |
| 116 | + if ($result) { |
| 117 | + // We record information about the successful recording in a separate log |
| 118 | + error_log(sprintf('File written successfully: %s, Size: %d bytes', $file, strlen($contents))); |
| 119 | + } |
| 120 | +}, 10, 3); |
| 121 | + |
| 122 | +WPFS::putContents(WP_CONTENT_DIR . '/uploads/my-log.txt', 'New log entry.'); |
| 123 | + |
| 124 | +// Disabling hooks |
| 125 | +FSHooksProvider::setStatus(false); |
| 126 | +``` |
| 127 | + |
| 128 | +## Instance Caching in FSFactory |
| 129 | + |
| 130 | +FSFactory caches created service instances to avoid unnecessary creation overhead. It's important to understand that the |
| 131 | +cache |
| 132 | +key depends not only on the class name but also on the current state of FSHooksProvider and FSGuardiansProvider. |
| 133 | + |
| 134 | +This means that within a single request, there can be up to 4 different instances of the same service, |
| 135 | +for example, FSBaseReader: |
| 136 | + |
| 137 | +1. Basic (hooks and protection disabled). |
| 138 | +2. With hooks only. |
| 139 | +3. With protection only. |
| 140 | +4. With both hooks and protection simultaneously. |
| 141 | + |
| 142 | +The factory manages this cache itself, ensuring that you always get the correct instance according to the |
| 143 | +current provider configuration. |
| 144 | + |
| 145 | +## Manual Instance Creation and Decoration |
| 146 | + |
| 147 | +While the WPFS facade and FSFactory cover 99% of scenarios, you might want to manually create and configure |
| 148 | +a service instance. This can be useful for dependency injection in your classes. |
| 149 | + |
| 150 | +The process follows the "Russian doll" principle (Decorator pattern): |
| 151 | + |
| 152 | +1. Create a base adapter object. |
| 153 | +2. Wrap it in a hook decorator (if needed). |
| 154 | +3. Wrap the resulting object in a protection decorator (if needed). |
| 155 | + |
| 156 | +```php |
| 157 | +global $wp_filesystem; |
| 158 | + |
| 159 | +// 1. Creating a base instance |
| 160 | +$baseAction = new \Metapraxis\WPFileSystem\Adapters\FSBaseAction($wp_filesystem); |
| 161 | + |
| 162 | +// 2. We wrap it in a hook decorator |
| 163 | +$hookableAction = new \Metapraxis\WPFileSystem\Hooks\HookableFSAction($baseAction); |
| 164 | + |
| 165 | +// 3. We wrap the result in a protection decorator |
| 166 | +$guardedAction = new \Metapraxis\WPFileSystem\Guarded\GuardedFSBaseAction($hookableAction); |
| 167 | + |
| 168 | + |
| 169 | +// Now $guardedAction is a full-featured service that |
| 170 | +// will both perform hooks and throw exceptions in case of errors. |
| 171 | +// It can be passed to the constructor of another class. |
| 172 | + |
| 173 | +class MyPluginService |
| 174 | +{ |
| 175 | + private $filesystem; |
| 176 | + |
| 177 | + public function __construct(\Metapraxis\WPFileSystem\Contracts\FSBaseAction $filesystem) |
| 178 | + { |
| 179 | + $this->filesystem = $filesystem; |
| 180 | + } |
| 181 | + |
| 182 | + public function doSomething() |
| 183 | + { |
| 184 | + try { |
| 185 | + $this->filesystem->putContents('/path/to/file.txt', 'data'); |
| 186 | + } catch (\Metapraxis\WPFileSystem\Exceptions\FSException $e) { |
| 187 | + // ... |
| 188 | + } |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +$service = new MyPluginService($guardedAction); |
| 193 | +$service->doSomething(); |
| 194 | +``` |
0 commit comments