Complex classes like FS_Storage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use FS_Storage, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class FS_Storage { |
||
23 | /** |
||
24 | * @var FS_Storage[] |
||
25 | */ |
||
26 | private static $_instances = array(); |
||
27 | /** |
||
28 | * @var FS_Key_Value_Storage Site level storage. |
||
29 | */ |
||
30 | private $_storage; |
||
31 | |||
32 | /** |
||
33 | * @var FS_Key_Value_Storage Network level storage. |
||
34 | */ |
||
35 | private $_network_storage; |
||
36 | |||
37 | /** |
||
38 | * @var string |
||
39 | */ |
||
40 | private $_module_type; |
||
41 | |||
42 | /** |
||
43 | * @var string |
||
44 | */ |
||
45 | private $_module_slug; |
||
46 | |||
47 | /** |
||
48 | * @var int The ID of the blog that is associated with the current site level options. |
||
49 | */ |
||
50 | private $_blog_id = 0; |
||
51 | |||
52 | /** |
||
53 | * @var bool |
||
54 | */ |
||
55 | private $_is_multisite; |
||
56 | |||
57 | /** |
||
58 | * @var bool |
||
59 | */ |
||
60 | private $_is_network_active = false; |
||
61 | |||
62 | /** |
||
63 | * @var bool |
||
64 | */ |
||
65 | private $_is_delegated_connection = false; |
||
66 | |||
67 | /** |
||
68 | * @var array { |
||
69 | * @key string Option name. |
||
70 | * @value int If 0 store on the network level. If 1, store on the network level only if module was network level activated. If 2, store on the network level only if network activated and NOT delegated the connection. |
||
71 | * } |
||
72 | */ |
||
73 | private static $_NETWORK_OPTIONS_MAP; |
||
74 | |||
75 | /** |
||
76 | * @author Leo Fajardo (@leorw) |
||
77 | * |
||
78 | * @param string $module_type |
||
79 | * @param string $slug |
||
80 | * |
||
81 | * @return FS_Storage |
||
82 | */ |
||
83 | static function instance( $module_type, $slug ) { |
||
92 | |||
93 | /** |
||
94 | * @author Leo Fajardo (@leorw) |
||
95 | * |
||
96 | * @param string $module_type |
||
97 | * @param string $slug |
||
98 | */ |
||
99 | private function __construct( $module_type, $slug ) { |
||
111 | |||
112 | /** |
||
113 | * Tells this storage wrapper class that the context plugin is network active. This flag will affect how values |
||
114 | * are retrieved/stored from/into the storage. |
||
115 | * |
||
116 | * @author Leo Fajardo (@leorw) |
||
117 | * |
||
118 | * @param bool $is_network_active |
||
119 | * @param bool $is_delegated_connection |
||
120 | */ |
||
121 | function set_network_active( $is_network_active = true, $is_delegated_connection = false ) { |
||
125 | |||
126 | /** |
||
127 | * Switch the context of the site level storage manager. |
||
128 | * |
||
129 | * @author Vova Feldman (@svovaf) |
||
130 | * @since 2.0.0 |
||
131 | * |
||
132 | * @param int $blog_id |
||
133 | */ |
||
134 | function set_site_blog_context( $blog_id ) { |
||
135 | $this->_storage = $this->get_site_storage( $blog_id ); |
||
136 | $this->_blog_id = $blog_id; |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * @author Leo Fajardo (@leorw) |
||
141 | * |
||
142 | * @param string $key |
||
143 | * @param mixed $value |
||
144 | * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). |
||
145 | * @param bool $flush |
||
146 | */ |
||
147 | function store( $key, $value, $network_level_or_blog_id = null, $flush = true ) { |
||
148 | if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { |
||
149 | $this->_network_storage->store( $key, $value, $flush ); |
||
150 | } else { |
||
151 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
||
152 | $storage->store( $key, $value, $flush ); |
||
153 | } |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * @author Leo Fajardo (@leorw) |
||
158 | * |
||
159 | * @param bool $store |
||
160 | * @param string[] $exceptions Set of keys to keep and not clear. |
||
161 | * @param int|null|bool $network_level_or_blog_id |
||
162 | */ |
||
163 | function clear_all( $store = true, $exceptions = array(), $network_level_or_blog_id = null ) { |
||
164 | if ( ! $this->_is_multisite || |
||
165 | false === $network_level_or_blog_id || |
||
166 | is_null( $network_level_or_blog_id ) || |
||
167 | is_numeric( $network_level_or_blog_id ) |
||
168 | ) { |
||
169 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
||
170 | $storage->clear_all( $store, $exceptions ); |
||
171 | } |
||
172 | |||
173 | if ( $this->_is_multisite && |
||
174 | ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) |
||
175 | ) { |
||
176 | $this->_network_storage->clear_all( $store, $exceptions ); |
||
177 | } |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * @author Leo Fajardo (@leorw) |
||
182 | * |
||
183 | * @param string $key |
||
184 | * @param bool $store |
||
185 | * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). |
||
186 | */ |
||
187 | function remove( $key, $store = true, $network_level_or_blog_id = null ) { |
||
188 | if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { |
||
189 | $this->_network_storage->remove( $key, $store ); |
||
190 | } else { |
||
191 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
||
192 | $storage->remove( $key, $store ); |
||
193 | } |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * @author Leo Fajardo (@leorw) |
||
198 | * |
||
199 | * @param string $key |
||
200 | * @param mixed $default |
||
201 | * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). |
||
202 | * |
||
203 | * @return mixed |
||
204 | */ |
||
205 | function get( $key, $default = false, $network_level_or_blog_id = null ) { |
||
206 | if ( $this->should_use_network_storage( $key, $network_level_or_blog_id ) ) { |
||
207 | return $this->_network_storage->get( $key, $default ); |
||
208 | } else { |
||
209 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
||
210 | |||
211 | return $storage->get( $key, $default ); |
||
212 | } |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Multisite activated: |
||
217 | * true: Save network storage. |
||
218 | * int: Save site specific storage. |
||
219 | * false|0: Save current site storage. |
||
220 | * null: Save network and current site storage. |
||
221 | * Site level activated: |
||
222 | * Save site storage. |
||
223 | * |
||
224 | * @author Vova Feldman (@svovaf) |
||
225 | * @since 2.0.0 |
||
226 | * |
||
227 | * @param bool|int|null $network_level_or_blog_id |
||
228 | */ |
||
229 | function save( $network_level_or_blog_id = null ) { |
||
230 | if ( $this->_is_network_active && |
||
231 | ( true === $network_level_or_blog_id || is_null( $network_level_or_blog_id ) ) |
||
232 | ) { |
||
233 | $this->_network_storage->save(); |
||
234 | } |
||
235 | |||
236 | if ( ! $this->_is_network_active || true !== $network_level_or_blog_id ) { |
||
237 | $storage = $this->get_site_storage( $network_level_or_blog_id ); |
||
238 | $storage->save(); |
||
239 | } |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * @author Vova Feldman (@svovaf) |
||
244 | * @since 2.0.0 |
||
245 | * |
||
246 | * @return string |
||
247 | */ |
||
248 | function get_module_slug() { |
||
249 | return $this->_module_slug; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * @author Vova Feldman (@svovaf) |
||
254 | * @since 2.0.0 |
||
255 | * |
||
256 | * @return string |
||
257 | */ |
||
258 | function get_module_type() { |
||
259 | return $this->_module_type; |
||
260 | } |
||
261 | |||
262 | /** |
||
263 | * Migration script to the new storage data structure that is network compatible. |
||
264 | * |
||
265 | * IMPORTANT: |
||
266 | * This method should be executed only after it is determined if this is a network |
||
267 | * level compatible product activation. |
||
268 | * |
||
269 | * @author Vova Feldman (@svovaf) |
||
270 | * @since 2.0.0 |
||
271 | */ |
||
272 | function migrate_to_network() { |
||
273 | if ( ! $this->_is_multisite ) { |
||
274 | return; |
||
275 | } |
||
276 | |||
277 | $updated = false; |
||
278 | |||
279 | if ( ! isset( self::$_NETWORK_OPTIONS_MAP ) ) { |
||
280 | self::load_network_options_map(); |
||
281 | } |
||
282 | |||
283 | foreach ( self::$_NETWORK_OPTIONS_MAP as $option => $storage_level ) { |
||
284 | if ( ! $this->is_multisite_option( $option ) ) { |
||
285 | continue; |
||
286 | } |
||
287 | |||
288 | if ( isset( $this->_storage->{$option} ) && ! isset( $this->_network_storage->{$option} ) ) { |
||
289 | // Migrate option to the network storage. |
||
290 | $this->_network_storage->store( $option, $this->_storage->{$option}, false ); |
||
291 | |||
292 | /** |
||
293 | * Remove the option from site level storage. |
||
294 | * |
||
295 | * IMPORTANT: |
||
296 | * The line below is intentionally commented since we want to preserve the option |
||
297 | * on the site storage level for "downgrade compatibility". Basically, if the user |
||
298 | * will downgrade to an older version of the plugin with the prev storage structure, |
||
299 | * it will continue working. |
||
300 | * |
||
301 | * @todo After a few releases we can remove this. |
||
302 | */ |
||
303 | // $this->_storage->remove($option, false); |
||
304 | |||
305 | $updated = true; |
||
306 | } |
||
307 | } |
||
308 | |||
309 | if ( ! $updated ) { |
||
310 | return; |
||
311 | } |
||
312 | |||
313 | // Update network level storage. |
||
314 | $this->_network_storage->save(); |
||
315 | // $this->_storage->save(); |
||
316 | } |
||
317 | |||
318 | #-------------------------------------------------------------------------------- |
||
319 | #region Helper Methods |
||
320 | #-------------------------------------------------------------------------------- |
||
321 | |||
322 | /** |
||
323 | * We don't want to load the map right away since it's not even needed in a non-MS environment. |
||
324 | * |
||
325 | * Example: |
||
326 | * array( |
||
327 | * 'option1' => 0, // Means that the option should always be stored on the network level. |
||
328 | * 'option2' => 1, // Means that the option should be stored on the network level only when the module was network level activated. |
||
329 | * 'option2' => 2, // Means that the option should be stored on the network level only when the module was network level activated AND the connection was NOT delegated. |
||
330 | * 'option3' => 3, // Means that the option should always be stored on the site level. |
||
331 | * ) |
||
332 | * |
||
333 | * @author Vova Feldman (@svovaf) |
||
334 | * @since 2.0.0 |
||
335 | */ |
||
336 | private static function load_network_options_map() { |
||
337 | self::$_NETWORK_OPTIONS_MAP = array( |
||
338 | // Network level options. |
||
339 | 'affiliate_application_data' => 0, |
||
340 | 'connectivity_test' => 0, |
||
341 | 'handle_gdpr_admin_notice' => 0, |
||
342 | 'has_trial_plan' => 0, |
||
343 | 'install_sync_timestamp' => 0, |
||
344 | 'install_sync_cron' => 0, |
||
345 | 'is_anonymous_ms' => 0, |
||
346 | 'is_on' => 0, |
||
347 | 'is_plugin_new_install' => 0, |
||
348 | 'network_install_blog_id' => 0, |
||
349 | 'pending_sites_info' => 0, |
||
350 | 'plugin_last_version' => 0, |
||
351 | 'plugin_main_file' => 0, |
||
352 | 'plugin_version' => 0, |
||
353 | 'sdk_downgrade_mode' => 0, |
||
354 | 'sdk_last_version' => 0, |
||
355 | 'sdk_upgrade_mode' => 0, |
||
356 | 'sdk_version' => 0, |
||
357 | 'sticky_optin_added_ms' => 0, |
||
358 | 'subscriptions' => 0, |
||
359 | 'sync_timestamp' => 0, |
||
360 | 'sync_cron' => 0, |
||
361 | 'was_plugin_loaded' => 0, |
||
362 | 'network_user_id' => 0, |
||
363 | 'plugin_upgrade_mode' => 0, |
||
364 | 'plugin_downgrade_mode' => 0, |
||
365 | 'is_network_connected' => 0, |
||
366 | /** |
||
367 | * Special flag that is used when a super-admin upgrades to the new version of the SDK that |
||
368 | * supports network level integration, when the connection decision wasn't made for all of the |
||
369 | * sites in the network. |
||
370 | */ |
||
371 | 'is_network_activation' => 0, |
||
372 | |||
373 | // When network activated, then network level. |
||
374 | 'install_timestamp' => 1, |
||
375 | 'prev_is_premium' => 1, |
||
376 | 'require_license_activation' => 1, |
||
377 | |||
378 | // If not network activated OR delegated, then site level. |
||
379 | 'activation_timestamp' => 2, |
||
380 | 'prev_user_id' => 2, |
||
381 | 'sticky_optin_added' => 2, |
||
382 | 'uninstall_reason' => 2, |
||
383 | 'is_pending_activation' => 2, |
||
384 | 'pending_license_key' => 2, |
||
385 | |||
386 | // Site level options. |
||
387 | 'is_anonymous' => 3, |
||
388 | ); |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * This method will and should only be executed when is_multisite() is true. |
||
393 | * |
||
394 | * @author Vova Feldman (@svovaf) |
||
395 | * @since 2.0.0 |
||
396 | * |
||
397 | * @param string $key |
||
398 | * |
||
399 | * @return bool|mixed |
||
400 | */ |
||
401 | private function is_multisite_option( $key ) { |
||
437 | |||
438 | /** |
||
439 | * @author Leo Fajardo |
||
440 | * |
||
441 | * @param string $key |
||
442 | * @param null|bool|int $network_level_or_blog_id When an integer, use the given blog storage. When `true` use the multisite storage (if there's a network). When `false`, use the current context blog storage. When `null`, the decision which storage to use (MS vs. Current S) will be handled internally and determined based on the $option (based on self::$_BINARY_MAP). |
||
443 | * |
||
444 | * @return bool |
||
445 | */ |
||
446 | private function should_use_network_storage( $key, $network_level_or_blog_id = null ) { |
||
465 | |||
466 | /** |
||
467 | * @author Vova Feldman (@svovaf) |
||
468 | * @since 2.0.0 |
||
469 | * |
||
470 | * @param int $blog_id |
||
471 | * |
||
472 | * @return \FS_Key_Value_Storage |
||
473 | */ |
||
474 | private function get_site_storage( $blog_id = 0 ) { |
||
488 | |||
489 | #endregion |
||
490 | |||
491 | #-------------------------------------------------------------------------------- |
||
492 | #region Magic methods |
||
493 | #-------------------------------------------------------------------------------- |
||
494 | |||
495 | function __set( $k, $v ) { |
||
502 | |||
503 | function __isset( $k ) { |
||
508 | |||
509 | function __unset( $k ) { |
||
516 | |||
517 | function __get( $k ) { |
||
522 | |||
523 | #endregion |
||
524 | } |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.