Version Description
Added Redis cache purge support.
Download this release
Release Info
Developer | rahul286 |
Plugin | Nginx Helper |
Version | 1.9 |
Comparing to | |
See all releases |
Code changes from version 1.8.13 to 1.9
- admin/assets/nginx.js +42 -26
- admin/assets/style.css +1 -1
- admin/lib/nginx-general.php +129 -65
- admin/lib/nginx-sidebar.php +59 -50
- includes/predis.php +15203 -0
- includes/redis-delete.php +104 -0
- nginx-helper.php +418 -383
- readme.txt +8 -5
- trunk/purger.php → redis-purger.php +88 -199
- tests/functional/custom-commands/enableAllPurgeCheckbox.js +74 -0
- tests/functional/custom-commands/goToAddNewPage.js +14 -0
- tests/functional/custom-commands/goToAddNewPost.js +16 -0
- tests/functional/custom-commands/nginxSettings.js +15 -0
- tests/functional/custom-commands/wplogin.js +14 -0
- tests/functional/custom-commands/wplogout.js +16 -0
- tests/functional/nightwatch.json +23 -0
- tests/functional/package.json +22 -0
- tests/functional/res/constants.js +15 -0
- tests/functional/src/purge-method-get-request-page-test.js +94 -0
- tests/functional/src/purge-method-get-request-post-test.js +145 -0
- tests/functional/src/purge-method-unlink-files-page-test.js +94 -0
- tests/functional/src/purge-method-unlink-files-post-test.js +145 -0
- trunk/admin/admin.php +0 -141
- trunk/admin/assets/logo.png +0 -0
- trunk/admin/assets/nginx-helper-icons/config.json +0 -34
- trunk/admin/assets/nginx-helper-icons/css/nginx-fontello.css +0 -56
- trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.eot +0 -0
- trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.svg +0 -15
- trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.ttf +0 -0
- trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.woff +0 -0
- trunk/admin/assets/nginx-icon-32x32.png +0 -0
- trunk/admin/assets/nginx.js +0 -26
- trunk/admin/assets/rtp-social-icons-32-32.png +0 -0
- trunk/admin/assets/style.css +0 -21
- trunk/admin/install.php +0 -98
- trunk/admin/lib/nginx-general.php +0 -374
- trunk/admin/lib/nginx-sidebar.php +0 -58
- trunk/admin/lib/nginx-support.php +0 -29
- trunk/compatibility.php +0 -37
- trunk/languages/nginx-helper.mo +0 -0
- trunk/languages/nginx-helper.po +0 -501
- trunk/nginx-helper.php +0 -400
- trunk/readme.txt +0 -297
- trunk/wercker.yml +0 -22
- trunk/wp-cli.php +0 -37
admin/assets/nginx.js
CHANGED
@@ -1,26 +1,42 @@
|
|
1 |
-
jQuery(document).ready(function() {
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery( document ).ready( function() {
|
2 |
+
var news_section = jQuery( '#latest_news' );
|
3 |
+
if ( news_section.length > 0 ) {
|
4 |
+
jQuery.get( news_url, function( data ) {
|
5 |
+
news_section.find( '.inside' ).html( data );
|
6 |
+
} );
|
7 |
+
}
|
8 |
+
|
9 |
+
jQuery( "form#purgeall a" ).click( function( e ) {
|
10 |
+
if ( confirm( "Purging entire cache is not recommended. Would you like to continue ?" ) == true ) {
|
11 |
+
// continue submitting form
|
12 |
+
} else {
|
13 |
+
e.preventDefault();
|
14 |
+
}
|
15 |
+
|
16 |
+
} );
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Show OR Hide options on option checkbox
|
20 |
+
* @param {type} selector Selector of Checkbox and PostBox
|
21 |
+
*/
|
22 |
+
function nginx_show_option( selector ) {
|
23 |
+
jQuery( '#' + selector ).on( 'change', function() {
|
24 |
+
if ( jQuery( this ).is( ':checked' ) ) {
|
25 |
+
jQuery( '.' + selector ).show();
|
26 |
+
if ( selector == "cache_method_redis" ) {
|
27 |
+
jQuery( '.cache_method_fastcgi' ).hide();
|
28 |
+
} else if ( selector == "cache_method_fastcgi" ) {
|
29 |
+
jQuery( '.cache_method_redis' ).hide();
|
30 |
+
}
|
31 |
+
} else {
|
32 |
+
jQuery( '.' + selector ).hide();
|
33 |
+
}
|
34 |
+
} );
|
35 |
+
}
|
36 |
+
/* Function call with parameter */
|
37 |
+
nginx_show_option( 'cache_method_fastcgi' );
|
38 |
+
nginx_show_option( 'cache_method_redis' );
|
39 |
+
nginx_show_option( 'enable_map' );
|
40 |
+
nginx_show_option( 'enable_log' );
|
41 |
+
nginx_show_option( 'enable_purge' );
|
42 |
+
} );
|
admin/assets/style.css
CHANGED
@@ -9,7 +9,7 @@ table.rtnginx-table:last-child { border-bottom: 0; }
|
|
9 |
pre#map { background: #e5e5e5 none; border-radius: 10px; padding: 10px; }
|
10 |
.wrap h2.rt_option_title { background: url(nginx-icon-32x32.png) 0 6px no-repeat rgba(0, 0, 0, 0); padding-left: 40px; }
|
11 |
#poststuff h2 { padding: 0 0 0 10px; margin-top: 0; }
|
12 |
-
form#purgeall .button-primary { box-shadow: inset 0 -2px rgba(0, 0, 0, 0.14);padding: 15px 30px;font-size: 1rem;border: 0;border-radius: 5px;color: #FFF;background: #DD3D36; height: auto; }
|
13 |
form#purgeall .button-primary:hover, form#purgeall .button-primary:focus { background: #d52c24; }
|
14 |
.nh-aligncenter { display: block; text-align: center; line-height: 2; }
|
15 |
#latest_news .inside ul, #useful-links .inside ul { margin: 0 0 0 12px }
|
9 |
pre#map { background: #e5e5e5 none; border-radius: 10px; padding: 10px; }
|
10 |
.wrap h2.rt_option_title { background: url(nginx-icon-32x32.png) 0 6px no-repeat rgba(0, 0, 0, 0); padding-left: 40px; }
|
11 |
#poststuff h2 { padding: 0 0 0 10px; margin-top: 0; }
|
12 |
+
form#purgeall .button-primary { margin-bottom: 20px; box-shadow: inset 0 -2px rgba(0, 0, 0, 0.14);padding: 15px 30px;font-size: 1rem;border: 0;border-radius: 5px;color: #FFF;background: #DD3D36; height: auto; }
|
13 |
form#purgeall .button-primary:hover, form#purgeall .button-primary:focus { background: #d52c24; }
|
14 |
.nh-aligncenter { display: block; text-align: center; line-height: 2; }
|
15 |
#latest_news .inside ul, #useful-links .inside ul { margin: 0 0 0 12px }
|
admin/lib/nginx-general.php
CHANGED
@@ -2,13 +2,15 @@
|
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
4 |
|
5 |
-
function nginx_general_options_page()
|
|
|
6 |
global $rt_wp_nginx_helper, $rt_wp_nginx_purger;
|
7 |
|
8 |
$update = 0;
|
9 |
$error_time = false;
|
10 |
$error_log_filesize = false;
|
11 |
$rt_wp_nginx_helper->options['enable_purge'] = (isset( $_POST['enable_purge'] ) and ( $_POST['enable_purge'] == 1) ) ? 1 : 0;
|
|
|
12 |
$rt_wp_nginx_helper->options['enable_map'] = (isset( $_POST['enable_map'] ) and ( $_POST['enable_map'] == 1) ) ? 1 : 0;
|
13 |
$rt_wp_nginx_helper->options['enable_log'] = (isset( $_POST['enable_log'] ) and ( $_POST['enable_log'] == 1) ) ? 1 : 0;
|
14 |
$rt_wp_nginx_helper->options['enable_stamp'] = (isset( $_POST['enable_stamp'] ) and ( $_POST['enable_stamp'] == 1) ) ? 1 : 0;
|
@@ -51,11 +53,20 @@ namespace rtCamp\WP\Nginx {
|
|
51 |
|
52 |
$rt_wp_nginx_helper->options['purge_method'] = ( isset( $_POST['purge_method'] ) ) ? $_POST['purge_method'] : 'get_request';
|
53 |
}
|
|
|
|
|
|
|
|
|
|
|
54 |
update_site_option( 'rt_wp_nginx_helper_options', $rt_wp_nginx_helper->options );
|
55 |
$update = 1;
|
56 |
}
|
57 |
$rt_wp_nginx_helper->options = get_site_option( 'rt_wp_nginx_helper_options' );
|
58 |
|
|
|
|
|
|
|
|
|
59 |
/**
|
60 |
* Show Update Message
|
61 |
*/
|
@@ -77,69 +88,115 @@ namespace rtCamp\WP\Nginx {
|
|
77 |
$nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/single-site/fastcgi-cache-with-purging/';
|
78 |
}
|
79 |
?>
|
80 |
-
<
|
81 |
-
<
|
82 |
-
<
|
83 |
-
|
84 |
-
|
85 |
<div class="inside">
|
86 |
-
<?php $purge_url = add_query_arg( array( 'nginx_helper_action' => 'purge', 'nginx_helper_urls' => 'all' ) ); ?>
|
87 |
-
<?php $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' ); ?>
|
88 |
<table class="form-table">
|
89 |
<tr valign="top">
|
90 |
-
<th><?php _e( 'Purge All Cache', 'nginx-helper' ); ?></th>
|
91 |
<td>
|
92 |
-
<
|
|
|
93 |
</td>
|
94 |
</tr>
|
95 |
</table>
|
96 |
-
</div>
|
97 |
-
</
|
98 |
-
|
99 |
-
<form id="post_form" method="post" action="#" name="smart_http_expire_form" class="clearfix">
|
100 |
-
<div class="postbox">
|
101 |
<h3 class="hndle">
|
102 |
-
<span><?php _e( '
|
103 |
</h3>
|
104 |
-
|
105 |
<div class="inside">
|
106 |
<input type="hidden" name="is_submit" value="1" />
|
107 |
<table class="form-table">
|
108 |
<tr valign="top">
|
109 |
<td>
|
110 |
-
<input type="
|
111 |
-
<label for="
|
112 |
-
|
113 |
</label>
|
114 |
</td>
|
115 |
</tr>
|
116 |
-
<?php if ( is_network_admin() ) { ?>
|
117 |
-
<tr valign="top">
|
118 |
-
<td>
|
119 |
-
<input type="checkbox" value="1" id="enable_map" name="enable_map"<?php checked( $rt_wp_nginx_helper->options['enable_map'], 1 ); ?> />
|
120 |
-
<label for="enable_map"><?php _e( 'Enable Nginx Map.', 'nginx-helper' ); ?></label>
|
121 |
-
</td>
|
122 |
-
</tr>
|
123 |
-
<?php } ?>
|
124 |
-
<tr valign="top">
|
125 |
-
<td>
|
126 |
-
<input type="checkbox" value="1" id="enable_log" name="enable_log"<?php checked( $rt_wp_nginx_helper->options['enable_log'], 1 ); ?> />
|
127 |
-
<label for="enable_log"><?php _e( 'Enable Logging', 'nginx-helper' ); ?></label>
|
128 |
-
</td>
|
129 |
-
</tr>
|
130 |
<tr valign="top">
|
131 |
<td>
|
132 |
-
<input type="
|
133 |
-
<label for="
|
|
|
|
|
134 |
</td>
|
135 |
</tr>
|
136 |
</table>
|
137 |
</div> <!-- End of .inside -->
|
138 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
<div class="postbox enable_purge"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == false ) ? ' style="display: none;"' : ''; ?>>
|
141 |
<h3 class="hndle">
|
142 |
-
<span><?php _e( 'Purging
|
143 |
</h3>
|
144 |
<div class="inside">
|
145 |
|
@@ -251,33 +308,39 @@ namespace rtCamp\WP\Nginx {
|
|
251 |
</td>
|
252 |
</tr>
|
253 |
</table>
|
254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
<tr valign="top">
|
256 |
-
<
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
<label for="
|
265 |
-
|
266 |
-
<?php _e( 'Using a GET request to <strong>PURGE/url</strong> (Default option)', 'nginx-helper' ); ?><br />
|
267 |
-
<small><?php _e( 'Uses the <strong><a href="https://github.com/FRiCKLE/ngx_cache_purge">ngx_cache_purge</a></strong> module. ', 'nginx-helper' ); ?></small>
|
268 |
-
</label><br />
|
269 |
-
<label for="purge_method_unlink_files">
|
270 |
-
<input type="radio" value="unlink_files" id="purge_method_unlink_files" name="purge_method"<?php checked( isset( $rt_wp_nginx_helper->options['purge_method'] ) ? $rt_wp_nginx_helper->options['purge_method'] : '', 'unlink_files' ); ?>>
|
271 |
-
<?php _e( 'Delete local server cache files', 'nginx-helper' ); ?><br />
|
272 |
-
<small><?php _e( 'Checks for matching cache file in <strong>RT_WP_NGINX_HELPER_CACHE_PATH</strong>. Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri"). ', 'nginx-helper' ); ?></small>
|
273 |
-
|
274 |
-
</label><br />
|
275 |
-
</fieldset>
|
276 |
-
</td>
|
277 |
</tr>
|
278 |
</table>
|
279 |
</div> <!-- End of .inside -->
|
280 |
-
</div
|
|
|
281 |
} // End of if ( !( !is_network_admin() && is_multisite() ) )
|
282 |
|
283 |
|
@@ -307,7 +370,7 @@ namespace rtCamp\WP\Nginx {
|
|
307 |
</table>
|
308 |
</div> <!-- End of .inside -->
|
309 |
</div>
|
310 |
-
|
311 |
|
312 |
<div class="postbox enable_log"<?php echo ( $rt_wp_nginx_helper->options['enable_log'] == false ) ? ' style="display: none;"' : ''; ?>>
|
313 |
<h3 class="hndle">
|
@@ -357,11 +420,12 @@ namespace rtCamp\WP\Nginx {
|
|
357 |
<tr>
|
358 |
<th><label for="log_filesize"><?php _e( 'Max log file size', 'nginx-helper' ); ?></label></th>
|
359 |
<td>
|
360 |
-
<input id="log_filesize" class="small-text" type="text" name="log_filesize" value="<?php echo $rt_wp_nginx_helper->options['log_filesize'] ?>" /> <?php
|
361 |
-
|
362 |
-
|
|
|
363 |
<p class="error fade" style="display: block;"><?php echo $error_log_filesize; ?></p><?php }
|
364 |
-
|
365 |
</td>
|
366 |
</tr>
|
367 |
</tbody>
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
4 |
|
5 |
+
function nginx_general_options_page()
|
6 |
+
{
|
7 |
global $rt_wp_nginx_helper, $rt_wp_nginx_purger;
|
8 |
|
9 |
$update = 0;
|
10 |
$error_time = false;
|
11 |
$error_log_filesize = false;
|
12 |
$rt_wp_nginx_helper->options['enable_purge'] = (isset( $_POST['enable_purge'] ) and ( $_POST['enable_purge'] == 1) ) ? 1 : 0;
|
13 |
+
$rt_wp_nginx_helper->options['cache_method'] = (isset( $_POST['cache_method'] ) ) ? $_POST['cache_method'] : 'enable_fastcgi';
|
14 |
$rt_wp_nginx_helper->options['enable_map'] = (isset( $_POST['enable_map'] ) and ( $_POST['enable_map'] == 1) ) ? 1 : 0;
|
15 |
$rt_wp_nginx_helper->options['enable_log'] = (isset( $_POST['enable_log'] ) and ( $_POST['enable_log'] == 1) ) ? 1 : 0;
|
16 |
$rt_wp_nginx_helper->options['enable_stamp'] = (isset( $_POST['enable_stamp'] ) and ( $_POST['enable_stamp'] == 1) ) ? 1 : 0;
|
53 |
|
54 |
$rt_wp_nginx_helper->options['purge_method'] = ( isset( $_POST['purge_method'] ) ) ? $_POST['purge_method'] : 'get_request';
|
55 |
}
|
56 |
+
if ( isset( $_POST['cache_method'] ) && $_POST['cache_method'] = "enable_redis" ) {
|
57 |
+
$rt_wp_nginx_helper->options['redis_hostname'] = ( isset( $_POST['redis_hostname'] ) ) ? $_POST['redis_hostname'] : '127.0.0.1';
|
58 |
+
$rt_wp_nginx_helper->options['redis_port'] = ( isset( $_POST['redis_port'] ) ) ? $_POST['redis_port'] : '6379';
|
59 |
+
$rt_wp_nginx_helper->options['redis_prefix'] = ( isset( $_POST['redis_prefix'] ) ) ? $_POST['redis_prefix'] : 'nginx-cache:';
|
60 |
+
}
|
61 |
update_site_option( 'rt_wp_nginx_helper_options', $rt_wp_nginx_helper->options );
|
62 |
$update = 1;
|
63 |
}
|
64 |
$rt_wp_nginx_helper->options = get_site_option( 'rt_wp_nginx_helper_options' );
|
65 |
|
66 |
+
// set default purge method to fastcgi
|
67 |
+
if ( empty( $rt_wp_nginx_helper->options['cache_method'] ) ) {
|
68 |
+
$rt_wp_nginx_helper->options['cache_method'] = "enable_fastcgi";
|
69 |
+
}
|
70 |
/**
|
71 |
* Show Update Message
|
72 |
*/
|
88 |
$nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/single-site/fastcgi-cache-with-purging/';
|
89 |
}
|
90 |
?>
|
91 |
+
<form id="post_form" method="post" action="#" name="smart_http_expire_form" class="clearfix">
|
92 |
+
<div class="postbox">
|
93 |
+
<h3 class="hndle">
|
94 |
+
<span><?php _e( 'Purging Options', 'nginx-helper' ); ?></span>
|
95 |
+
</h3>
|
96 |
<div class="inside">
|
|
|
|
|
97 |
<table class="form-table">
|
98 |
<tr valign="top">
|
|
|
99 |
<td>
|
100 |
+
<input type="checkbox" value="1" id="enable_purge" name="enable_purge"<?php checked( $rt_wp_nginx_helper->options['enable_purge'], 1 ); ?> />
|
101 |
+
<label for="enable_purge"><?php _e( 'Enable Purge', 'nginx-helper' ); ?></label>
|
102 |
</td>
|
103 |
</tr>
|
104 |
</table>
|
105 |
+
</div> <!-- End of .inside -->
|
106 |
+
</div>
|
107 |
+
<div class="postbox enable_purge"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == false ) ? ' style="display: none;"' : ''; ?>>
|
|
|
|
|
108 |
<h3 class="hndle">
|
109 |
+
<span><?php _e( 'Caching Method', 'nginx-helper' ); ?></span>
|
110 |
</h3>
|
111 |
+
<?php if ( !(!is_network_admin() && is_multisite() ) ) { ?>
|
112 |
<div class="inside">
|
113 |
<input type="hidden" name="is_submit" value="1" />
|
114 |
<table class="form-table">
|
115 |
<tr valign="top">
|
116 |
<td>
|
117 |
+
<input type="radio" value="enable_fastcgi" id="cache_method_fastcgi" name="cache_method" <?php checked( $rt_wp_nginx_helper->options['cache_method'], "enable_fastcgi" ); ?> />
|
118 |
+
<label for="cache_method_fastcgi">
|
119 |
+
<?php printf( __( 'nginx Fastcgi cache (<a target="_blank" href="%s" title="External settings for nginx">requires external settings for nginx</a>)', 'nginx-helper' ), $nginx_setting_link ); ?>
|
120 |
</label>
|
121 |
</td>
|
122 |
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
<tr valign="top">
|
124 |
<td>
|
125 |
+
<input type="radio" value="enable_redis" id="cache_method_redis" name="cache_method" <?php checked( $rt_wp_nginx_helper->options['cache_method'], "enable_redis" ); ?> />
|
126 |
+
<label for="cache_method_redis">
|
127 |
+
<?php printf( __( 'Redis cache', 'nginx-helper' ) ); ?>
|
128 |
+
</label>
|
129 |
</td>
|
130 |
</tr>
|
131 |
</table>
|
132 |
</div> <!-- End of .inside -->
|
133 |
</div>
|
134 |
+
<div class="enable_purge">
|
135 |
+
<div class="postbox cache_method_fastcgi"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == true && $rt_wp_nginx_helper->options['cache_method'] == "enable_fastcgi" ) ? '' : ' style="display: none;"'; ?>>
|
136 |
+
<h3 class="hndle">
|
137 |
+
<span><?php _e( 'Purge Method', 'nginx-helper' ); ?></span>
|
138 |
+
</h3>
|
139 |
+
<div class="inside">
|
140 |
+
<table class="form-table rtnginx-table">
|
141 |
+
<tr valign="top">
|
142 |
+
<td>
|
143 |
+
<fieldset>
|
144 |
+
<legend class="screen-reader-text">
|
145 |
+
<span> <?php _e( 'when a post/page/custom post is published.', 'nginx-helper' ); ?></span>
|
146 |
+
</legend>
|
147 |
+
<label for="purge_method_get_request">
|
148 |
+
<input type="radio" value="get_request" id="purge_method_get_request" name="purge_method"<?php checked( isset( $rt_wp_nginx_helper->options['purge_method'] ) ? $rt_wp_nginx_helper->options['purge_method'] : 'get_request', 'get_request' ); ?>>
|
149 |
+
<?php _e( 'Using a GET request to <strong>PURGE/url</strong> (Default option)', 'nginx-helper' ); ?><br />
|
150 |
+
<small><?php _e( 'Uses the <strong><a href="https://github.com/FRiCKLE/ngx_cache_purge">ngx_cache_purge</a></strong> module. ', 'nginx-helper' ); ?></small>
|
151 |
+
</label><br />
|
152 |
+
<label for="purge_method_unlink_files">
|
153 |
+
<input type="radio" value="unlink_files" id="purge_method_unlink_files" name="purge_method"<?php checked( isset( $rt_wp_nginx_helper->options['purge_method'] ) ? $rt_wp_nginx_helper->options['purge_method'] : '', 'unlink_files' ); ?>>
|
154 |
+
<?php _e( 'Delete local server cache files', 'nginx-helper' ); ?><br />
|
155 |
+
<small><?php _e( 'Checks for matching cache file in <strong>RT_WP_NGINX_HELPER_CACHE_PATH</strong>. Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri"). ', 'nginx-helper' ); ?></small>
|
156 |
|
157 |
+
</label><br />
|
158 |
+
</fieldset>
|
159 |
+
</td>
|
160 |
+
</tr>
|
161 |
+
</table>
|
162 |
+
</div> <!-- End of .inside -->
|
163 |
+
</div>
|
164 |
+
<div class="postbox cache_method_redis"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == true && $rt_wp_nginx_helper->options['cache_method'] == "enable_redis" ) ? '' : ' style="display: none;"'; ?>>
|
165 |
+
<h3 class="hndle">
|
166 |
+
<span><?php _e( 'Redis Settings', 'nginx-helper' ); ?></span>
|
167 |
+
</h3>
|
168 |
+
<div class="inside">
|
169 |
+
<table class="form-table rtnginx-table">
|
170 |
+
<?php
|
171 |
+
$redis_hostname = ( empty( $rt_wp_nginx_helper->options['redis_hostname'] ) ) ? '127.0.0.1' : $rt_wp_nginx_helper->options['redis_hostname'];
|
172 |
+
$redis_port = ( empty( $rt_wp_nginx_helper->options['redis_port'] ) ) ? '6379' : $rt_wp_nginx_helper->options['redis_port'];
|
173 |
+
$redis_prefix = ( empty( $rt_wp_nginx_helper->options['redis_prefix'] ) ) ? 'nginx-cache:' : $rt_wp_nginx_helper->options['redis_prefix'];
|
174 |
+
?>
|
175 |
+
<tr>
|
176 |
+
<th><label for="redis_hostname"><?php _e( 'Hostname', 'nginx-helper' ); ?></label></th>
|
177 |
+
<td>
|
178 |
+
<input id="redis_hostname" class="medium-text" type="text" name="redis_hostname" value="<?php echo $redis_hostname; ?>" />
|
179 |
+
</td>
|
180 |
+
</tr>
|
181 |
+
<tr>
|
182 |
+
<th><label for="redis_port"><?php _e( 'Port', 'nginx-helper' ); ?></label></th>
|
183 |
+
<td>
|
184 |
+
<input id="redis_port" class="medium-text" type="text" name="redis_port" value="<?php echo $redis_port; ?>" />
|
185 |
+
</td>
|
186 |
+
</tr>
|
187 |
+
<tr>
|
188 |
+
<th><label for="redis_prefix"><?php _e( 'Prefix', 'nginx-helper' ); ?></label></th>
|
189 |
+
<td>
|
190 |
+
<input id="redis_prefix" class="medium-text" type="text" name="redis_prefix" value="<?php echo $redis_prefix; ?>" />
|
191 |
+
</td>
|
192 |
+
</tr>
|
193 |
+
</table>
|
194 |
+
</div> <!-- End of .inside -->
|
195 |
+
</div>
|
196 |
+
</div>
|
197 |
<div class="postbox enable_purge"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == false ) ? ' style="display: none;"' : ''; ?>>
|
198 |
<h3 class="hndle">
|
199 |
+
<span><?php _e( 'Purging Conditions', 'nginx-helper' ); ?></span>
|
200 |
</h3>
|
201 |
<div class="inside">
|
202 |
|
308 |
</td>
|
309 |
</tr>
|
310 |
</table>
|
311 |
+
</div> <!-- End of .inside -->
|
312 |
+
</div>
|
313 |
+
<div class="postbox">
|
314 |
+
<h3 class="hndle">
|
315 |
+
<span><?php _e( 'Debug Options', 'nginx-helper' ); ?></span>
|
316 |
+
</h3>
|
317 |
+
<div class="inside">
|
318 |
+
<input type="hidden" name="is_submit" value="1" />
|
319 |
+
<table class="form-table">
|
320 |
+
<?php if ( is_network_admin() ) { ?>
|
321 |
+
<tr valign="top">
|
322 |
+
<td>
|
323 |
+
<input type="checkbox" value="1" id="enable_map" name="enable_map"<?php checked( $rt_wp_nginx_helper->options['enable_map'], 1 ); ?> />
|
324 |
+
<label for="enable_map"><?php _e( 'Enable Nginx Map.', 'nginx-helper' ); ?></label>
|
325 |
+
</td>
|
326 |
+
</tr>
|
327 |
+
<?php } ?>
|
328 |
<tr valign="top">
|
329 |
+
<td>
|
330 |
+
<input type="checkbox" value="1" id="enable_log" name="enable_log"<?php checked( $rt_wp_nginx_helper->options['enable_log'], 1 ); ?> />
|
331 |
+
<label for="enable_log"><?php _e( 'Enable Logging', 'nginx-helper' ); ?></label>
|
332 |
+
</td>
|
333 |
+
</tr>
|
334 |
+
<tr valign="top">
|
335 |
+
<td>
|
336 |
+
<input type="checkbox" value="1" id="enable_stamp" name="enable_stamp"<?php checked( $rt_wp_nginx_helper->options['enable_stamp'], 1 ); ?> />
|
337 |
+
<label for="enable_stamp"><?php _e( 'Enable Nginx Timestamp in HTML', 'nginx-helper' ); ?></label>
|
338 |
+
</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
</tr>
|
340 |
</table>
|
341 |
</div> <!-- End of .inside -->
|
342 |
+
</div>
|
343 |
+
<?php
|
344 |
} // End of if ( !( !is_network_admin() && is_multisite() ) )
|
345 |
|
346 |
|
370 |
</table>
|
371 |
</div> <!-- End of .inside -->
|
372 |
</div>
|
373 |
+
<?php } ?>
|
374 |
|
375 |
<div class="postbox enable_log"<?php echo ( $rt_wp_nginx_helper->options['enable_log'] == false ) ? ' style="display: none;"' : ''; ?>>
|
376 |
<h3 class="hndle">
|
420 |
<tr>
|
421 |
<th><label for="log_filesize"><?php _e( 'Max log file size', 'nginx-helper' ); ?></label></th>
|
422 |
<td>
|
423 |
+
<input id="log_filesize" class="small-text" type="text" name="log_filesize" value="<?php echo $rt_wp_nginx_helper->options['log_filesize'] ?>" /> <?php
|
424 |
+
_e( 'Mb', 'nginx-helper' );
|
425 |
+
if ( $error_log_filesize ) {
|
426 |
+
?>
|
427 |
<p class="error fade" style="display: block;"><?php echo $error_log_filesize; ?></p><?php }
|
428 |
+
?>
|
429 |
</td>
|
430 |
</tr>
|
431 |
</tbody>
|
admin/lib/nginx-sidebar.php
CHANGED
@@ -2,57 +2,66 @@
|
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
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 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
58 |
}
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
4 |
|
5 |
+
function default_admin_sidebar()
|
6 |
+
{
|
7 |
+
?>
|
8 |
+
<?php $purge_url = add_query_arg( array( 'nginx_helper_action' => 'purge', 'nginx_helper_urls' => 'all' ) ); ?>
|
9 |
+
<?php $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' ); ?>
|
10 |
+
<form id="purgeall" action="" method="post" class="clearfix">
|
11 |
+
<a href="<?php echo $nonced_url; ?>" class="button-primary"><?php _e( 'Purge Entire Cache', 'nginx-helper' ); ?></a>
|
12 |
+
</form>
|
13 |
+
<div class="postbox" id="support">
|
14 |
+
<h3 class="hndle">
|
15 |
+
<span><?php _e( 'Need Help?', 'nginx-helper' ); ?></span>
|
16 |
+
</h3>
|
17 |
+
<div class="inside">
|
18 |
+
<p><?php printf( __( 'Please use our <a href="%s">free support forum</a>.', 'nginx-helper' ), 'http://rtcamp.com/support/forum/wordpress-nginx/' ); ?></p>
|
19 |
+
</div>
|
20 |
+
</div>
|
21 |
|
22 |
+
<div class="postbox" id="social">
|
23 |
+
<h3 class="hndle">
|
24 |
+
<span><?php _e( 'Getting Social is Good', 'nginx-helper' ); ?></span>
|
25 |
+
</h3>
|
26 |
+
<div style="text-align:center;" class="inside">
|
27 |
+
<a class="nginx-helper-facebook" title="<?php _e( 'Become a fan on Facebook', 'nginx-helper' ); ?>" target="_blank" href="http://www.facebook.com/rtCamp.solutions/"></a>
|
28 |
+
<a class="nginx-helper-twitter" title="<?php _e( 'Follow us on Twitter', 'nginx-helper' ); ?>" target="_blank" href="https://twitter.com/rtcamp/"></a>
|
29 |
+
<a class="nginx-helper-gplus" title="<?php _e( 'Add to Circle', 'nginx-helper' ); ?>" target="_blank" href="https://plus.google.com/110214156830549460974/posts"></a>
|
30 |
+
<a class="nginx-helper-rss" title="<?php _e( 'Subscribe to our feeds', 'nginx-helper' ); ?>" target="_blank" href="http://feeds.feedburner.com/rtcamp/"></a>
|
31 |
+
</div>
|
32 |
+
</div>
|
33 |
|
34 |
+
<div class="postbox" id="useful-links">
|
35 |
+
<h3 class="hndle">
|
36 |
+
<span><?php _e( 'Useful Links', 'nginx-helper' ); ?></span>
|
37 |
+
</h3>
|
38 |
+
<div class="inside">
|
39 |
+
<ul role="list">
|
40 |
+
<li role="listitem">
|
41 |
+
<a href="https://rtcamp.com/wordpress-nginx/" title="<?php _e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?>"><?php _e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?></a>
|
42 |
+
</li>
|
43 |
+
<li role="listitem">
|
44 |
+
<a href="https://rtcamp.com/services/wordPress-themes-design-development/" title="<?php _e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?>"><?php _e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?></a>
|
45 |
+
</li>
|
46 |
+
<li role="listitem">
|
47 |
+
<a href="http://rtcamp.com/services/wordpress-plugins/" title="<?php _e( 'WordPress Plugin Development', 'nginx-helper' ); ?>"><?php _e( 'WordPress Plugin Development', 'nginx-helper' ); ?></a>
|
48 |
+
</li>
|
49 |
+
<li role="listitem">
|
50 |
+
<a href="http://rtcamp.com/services/custom-wordpress-solutions/" title="<?php _e( 'WordPress Consultancy', 'nginx-helper' ); ?>"><?php _e( 'WordPress Consultancy', 'nginx-helper' ); ?></a>
|
51 |
+
</li>
|
52 |
+
<li role="listitem">
|
53 |
+
<a href="https://rtcamp.com/easyengine/" title="<?php _e( 'easyengine (ee)', 'nginx-helper' ); ?>"><?php _e( 'easyengine (ee)', 'nginx-helper' ); ?></a>
|
54 |
+
</li>
|
55 |
+
</ul>
|
56 |
+
</div>
|
57 |
+
</div>
|
58 |
|
59 |
+
<div class="postbox" id="latest_news">
|
60 |
+
<div title="<?php _e( 'Click to toggle', 'nginx-helper' ); ?>" class="handlediv"><br /></div>
|
61 |
+
<h3 class="hndle"><span><?php _e( 'Latest News', 'nginx-helper' ); ?></span></h3>
|
62 |
+
<div class="inside"><img src ="<?php echo admin_url(); ?>/images/wpspin_light.gif" /><?php _e( 'Loading...', 'nginx-helper' ); ?></div>
|
63 |
+
</div><?php
|
64 |
+
}
|
65 |
+
|
66 |
+
// End of default_admin_sidebar()
|
67 |
}
|
includes/predis.php
ADDED
@@ -0,0 +1,15203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* This file is part of the Predis package.
|
5 |
+
*
|
6 |
+
* (c) Daniele Alessandri <suppakilla@gmail.com>
|
7 |
+
*
|
8 |
+
* For the full copyright and license information, please view the LICENSE
|
9 |
+
* file that was distributed with this source code.
|
10 |
+
*/
|
11 |
+
|
12 |
+
namespace Predis\Command;
|
13 |
+
|
14 |
+
use InvalidArgumentException;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Defines an abstraction representing a Redis command.
|
18 |
+
*
|
19 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
20 |
+
*/
|
21 |
+
interface CommandInterface
|
22 |
+
{
|
23 |
+
/**
|
24 |
+
* Returns the ID of the Redis command. By convention, command identifiers
|
25 |
+
* must always be uppercase.
|
26 |
+
*
|
27 |
+
* @return string
|
28 |
+
*/
|
29 |
+
public function getId();
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Assign the specified slot to the command for clustering distribution.
|
33 |
+
*
|
34 |
+
* @param int $slot Slot ID.
|
35 |
+
*/
|
36 |
+
public function setSlot($slot);
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Returns the assigned slot of the command for clustering distribution.
|
40 |
+
*
|
41 |
+
* @return int|null
|
42 |
+
*/
|
43 |
+
public function getSlot();
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Sets the arguments for the command.
|
47 |
+
*
|
48 |
+
* @param array $arguments List of arguments.
|
49 |
+
*/
|
50 |
+
public function setArguments(array $arguments);
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Sets the raw arguments for the command without processing them.
|
54 |
+
*
|
55 |
+
* @param array $arguments List of arguments.
|
56 |
+
*/
|
57 |
+
public function setRawArguments(array $arguments);
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Gets the arguments of the command.
|
61 |
+
*
|
62 |
+
* @return array
|
63 |
+
*/
|
64 |
+
public function getArguments();
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Gets the argument of the command at the specified index.
|
68 |
+
*
|
69 |
+
* @param int $index Index of the desired argument.
|
70 |
+
*
|
71 |
+
* @return mixed|null
|
72 |
+
*/
|
73 |
+
public function getArgument($index);
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Parses a raw response and returns a PHP object.
|
77 |
+
*
|
78 |
+
* @param string $data Binary string containing the whole response.
|
79 |
+
*
|
80 |
+
* @return mixed
|
81 |
+
*/
|
82 |
+
public function parseResponse($data);
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Base class for Redis commands.
|
87 |
+
*
|
88 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
89 |
+
*/
|
90 |
+
abstract class Command implements CommandInterface
|
91 |
+
{
|
92 |
+
private $slot;
|
93 |
+
private $arguments = array();
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Returns a filtered array of the arguments.
|
97 |
+
*
|
98 |
+
* @param array $arguments List of arguments.
|
99 |
+
*
|
100 |
+
* @return array
|
101 |
+
*/
|
102 |
+
protected function filterArguments(array $arguments)
|
103 |
+
{
|
104 |
+
return $arguments;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* {@inheritdoc}
|
109 |
+
*/
|
110 |
+
public function setArguments(array $arguments)
|
111 |
+
{
|
112 |
+
$this->arguments = $this->filterArguments($arguments);
|
113 |
+
unset($this->slot);
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* {@inheritdoc}
|
118 |
+
*/
|
119 |
+
public function setRawArguments(array $arguments)
|
120 |
+
{
|
121 |
+
$this->arguments = $arguments;
|
122 |
+
unset($this->slot);
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* {@inheritdoc}
|
127 |
+
*/
|
128 |
+
public function getArguments()
|
129 |
+
{
|
130 |
+
return $this->arguments;
|
131 |
+
}
|
132 |
+
|
133 |
+
/**
|
134 |
+
* {@inheritdoc}
|
135 |
+
*/
|
136 |
+
public function getArgument($index)
|
137 |
+
{
|
138 |
+
if (isset($this->arguments[$index])) {
|
139 |
+
return $this->arguments[$index];
|
140 |
+
}
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* {@inheritdoc}
|
145 |
+
*/
|
146 |
+
public function setSlot($slot)
|
147 |
+
{
|
148 |
+
$this->slot = $slot;
|
149 |
+
}
|
150 |
+
|
151 |
+
/**
|
152 |
+
* {@inheritdoc}
|
153 |
+
*/
|
154 |
+
public function getSlot()
|
155 |
+
{
|
156 |
+
if (isset($this->slot)) {
|
157 |
+
return $this->slot;
|
158 |
+
}
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* {@inheritdoc}
|
163 |
+
*/
|
164 |
+
public function parseResponse($data)
|
165 |
+
{
|
166 |
+
return $data;
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Normalizes the arguments array passed to a Redis command.
|
171 |
+
*
|
172 |
+
* @param array $arguments Arguments for a command.
|
173 |
+
*
|
174 |
+
* @return array
|
175 |
+
*/
|
176 |
+
public static function normalizeArguments(array $arguments)
|
177 |
+
{
|
178 |
+
if (count($arguments) === 1 && is_array($arguments[0])) {
|
179 |
+
return $arguments[0];
|
180 |
+
}
|
181 |
+
|
182 |
+
return $arguments;
|
183 |
+
}
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Normalizes the arguments array passed to a variadic Redis command.
|
187 |
+
*
|
188 |
+
* @param array $arguments Arguments for a command.
|
189 |
+
*
|
190 |
+
* @return array
|
191 |
+
*/
|
192 |
+
public static function normalizeVariadic(array $arguments)
|
193 |
+
{
|
194 |
+
if (count($arguments) === 2 && is_array($arguments[1])) {
|
195 |
+
return array_merge(array($arguments[0]), $arguments[1]);
|
196 |
+
}
|
197 |
+
|
198 |
+
return $arguments;
|
199 |
+
}
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* @link http://redis.io/commands/zrange
|
204 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
205 |
+
*/
|
206 |
+
class ZSetRange extends Command
|
207 |
+
{
|
208 |
+
/**
|
209 |
+
* {@inheritdoc}
|
210 |
+
*/
|
211 |
+
public function getId()
|
212 |
+
{
|
213 |
+
return 'ZRANGE';
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* {@inheritdoc}
|
218 |
+
*/
|
219 |
+
protected function filterArguments(array $arguments)
|
220 |
+
{
|
221 |
+
if (count($arguments) === 4) {
|
222 |
+
$lastType = gettype($arguments[3]);
|
223 |
+
|
224 |
+
if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') {
|
225 |
+
// Used for compatibility with older versions
|
226 |
+
$arguments[3] = array('WITHSCORES' => true);
|
227 |
+
$lastType = 'array';
|
228 |
+
}
|
229 |
+
|
230 |
+
if ($lastType === 'array') {
|
231 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
232 |
+
|
233 |
+
return array_merge($arguments, $options);
|
234 |
+
}
|
235 |
+
}
|
236 |
+
|
237 |
+
return $arguments;
|
238 |
+
}
|
239 |
+
|
240 |
+
/**
|
241 |
+
* Returns a list of options and modifiers compatible with Redis.
|
242 |
+
*
|
243 |
+
* @param array $options List of options.
|
244 |
+
*
|
245 |
+
* @return array
|
246 |
+
*/
|
247 |
+
protected function prepareOptions($options)
|
248 |
+
{
|
249 |
+
$opts = array_change_key_case($options, CASE_UPPER);
|
250 |
+
$finalizedOpts = array();
|
251 |
+
|
252 |
+
if (!empty($opts['WITHSCORES'])) {
|
253 |
+
$finalizedOpts[] = 'WITHSCORES';
|
254 |
+
}
|
255 |
+
|
256 |
+
return $finalizedOpts;
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Checks for the presence of the WITHSCORES modifier.
|
261 |
+
*
|
262 |
+
* @return bool
|
263 |
+
*/
|
264 |
+
protected function withScores()
|
265 |
+
{
|
266 |
+
$arguments = $this->getArguments();
|
267 |
+
|
268 |
+
if (count($arguments) < 4) {
|
269 |
+
return false;
|
270 |
+
}
|
271 |
+
|
272 |
+
return strtoupper($arguments[3]) === 'WITHSCORES';
|
273 |
+
}
|
274 |
+
|
275 |
+
/**
|
276 |
+
* {@inheritdoc}
|
277 |
+
*/
|
278 |
+
public function parseResponse($data)
|
279 |
+
{
|
280 |
+
if ($this->withScores()) {
|
281 |
+
$result = array();
|
282 |
+
|
283 |
+
for ($i = 0; $i < count($data); $i++) {
|
284 |
+
$result[$data[$i]] = $data[++$i];
|
285 |
+
}
|
286 |
+
|
287 |
+
return $result;
|
288 |
+
}
|
289 |
+
|
290 |
+
return $data;
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* @link http://redis.io/commands/sinterstore
|
296 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
297 |
+
*/
|
298 |
+
class SetIntersectionStore extends Command
|
299 |
+
{
|
300 |
+
/**
|
301 |
+
* {@inheritdoc}
|
302 |
+
*/
|
303 |
+
public function getId()
|
304 |
+
{
|
305 |
+
return 'SINTERSTORE';
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* {@inheritdoc}
|
310 |
+
*/
|
311 |
+
protected function filterArguments(array $arguments)
|
312 |
+
{
|
313 |
+
if (count($arguments) === 2 && is_array($arguments[1])) {
|
314 |
+
return array_merge(array($arguments[0]), $arguments[1]);
|
315 |
+
}
|
316 |
+
|
317 |
+
return $arguments;
|
318 |
+
}
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* @link http://redis.io/commands/sinter
|
323 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
324 |
+
*/
|
325 |
+
class SetIntersection extends Command
|
326 |
+
{
|
327 |
+
/**
|
328 |
+
* {@inheritdoc}
|
329 |
+
*/
|
330 |
+
public function getId()
|
331 |
+
{
|
332 |
+
return 'SINTER';
|
333 |
+
}
|
334 |
+
|
335 |
+
/**
|
336 |
+
* {@inheritdoc}
|
337 |
+
*/
|
338 |
+
protected function filterArguments(array $arguments)
|
339 |
+
{
|
340 |
+
return self::normalizeArguments($arguments);
|
341 |
+
}
|
342 |
+
}
|
343 |
+
|
344 |
+
/**
|
345 |
+
* @link http://redis.io/commands/eval
|
346 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
347 |
+
*/
|
348 |
+
class ServerEval extends Command
|
349 |
+
{
|
350 |
+
/**
|
351 |
+
* {@inheritdoc}
|
352 |
+
*/
|
353 |
+
public function getId()
|
354 |
+
{
|
355 |
+
return 'EVAL';
|
356 |
+
}
|
357 |
+
|
358 |
+
/**
|
359 |
+
* Calculates the SHA1 hash of the body of the script.
|
360 |
+
*
|
361 |
+
* @return string SHA1 hash.
|
362 |
+
*/
|
363 |
+
public function getScriptHash()
|
364 |
+
{
|
365 |
+
return sha1($this->getArgument(0));
|
366 |
+
}
|
367 |
+
}
|
368 |
+
|
369 |
+
/**
|
370 |
+
* @link http://redis.io/commands/rename
|
371 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
372 |
+
*/
|
373 |
+
class KeyRename extends Command
|
374 |
+
{
|
375 |
+
/**
|
376 |
+
* {@inheritdoc}
|
377 |
+
*/
|
378 |
+
public function getId()
|
379 |
+
{
|
380 |
+
return 'RENAME';
|
381 |
+
}
|
382 |
+
}
|
383 |
+
|
384 |
+
/**
|
385 |
+
* @link http://redis.io/commands/setex
|
386 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
387 |
+
*/
|
388 |
+
class StringSetExpire extends Command
|
389 |
+
{
|
390 |
+
/**
|
391 |
+
* {@inheritdoc}
|
392 |
+
*/
|
393 |
+
public function getId()
|
394 |
+
{
|
395 |
+
return 'SETEX';
|
396 |
+
}
|
397 |
+
}
|
398 |
+
|
399 |
+
/**
|
400 |
+
* @link http://redis.io/commands/mset
|
401 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
402 |
+
*/
|
403 |
+
class StringSetMultiple extends Command
|
404 |
+
{
|
405 |
+
/**
|
406 |
+
* {@inheritdoc}
|
407 |
+
*/
|
408 |
+
public function getId()
|
409 |
+
{
|
410 |
+
return 'MSET';
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* {@inheritdoc}
|
415 |
+
*/
|
416 |
+
protected function filterArguments(array $arguments)
|
417 |
+
{
|
418 |
+
if (count($arguments) === 1 && is_array($arguments[0])) {
|
419 |
+
$flattenedKVs = array();
|
420 |
+
$args = $arguments[0];
|
421 |
+
|
422 |
+
foreach ($args as $k => $v) {
|
423 |
+
$flattenedKVs[] = $k;
|
424 |
+
$flattenedKVs[] = $v;
|
425 |
+
}
|
426 |
+
|
427 |
+
return $flattenedKVs;
|
428 |
+
}
|
429 |
+
|
430 |
+
return $arguments;
|
431 |
+
}
|
432 |
+
}
|
433 |
+
|
434 |
+
/**
|
435 |
+
* @link http://redis.io/commands/expireat
|
436 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
437 |
+
*/
|
438 |
+
class KeyExpireAt extends Command
|
439 |
+
{
|
440 |
+
/**
|
441 |
+
* {@inheritdoc}
|
442 |
+
*/
|
443 |
+
public function getId()
|
444 |
+
{
|
445 |
+
return 'EXPIREAT';
|
446 |
+
}
|
447 |
+
|
448 |
+
/**
|
449 |
+
* {@inheritdoc}
|
450 |
+
*/
|
451 |
+
public function parseResponse($data)
|
452 |
+
{
|
453 |
+
return (bool) $data;
|
454 |
+
}
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* @link http://redis.io/commands/blpop
|
459 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
460 |
+
*/
|
461 |
+
class ListPopFirstBlocking extends Command
|
462 |
+
{
|
463 |
+
/**
|
464 |
+
* {@inheritdoc}
|
465 |
+
*/
|
466 |
+
public function getId()
|
467 |
+
{
|
468 |
+
return 'BLPOP';
|
469 |
+
}
|
470 |
+
|
471 |
+
/**
|
472 |
+
* {@inheritdoc}
|
473 |
+
*/
|
474 |
+
protected function filterArguments(array $arguments)
|
475 |
+
{
|
476 |
+
if (count($arguments) === 2 && is_array($arguments[0])) {
|
477 |
+
list($arguments, $timeout) = $arguments;
|
478 |
+
array_push($arguments, $timeout);
|
479 |
+
}
|
480 |
+
|
481 |
+
return $arguments;
|
482 |
+
}
|
483 |
+
}
|
484 |
+
|
485 |
+
/**
|
486 |
+
* @link http://redis.io/commands/unsubscribe
|
487 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
488 |
+
*/
|
489 |
+
class PubSubUnsubscribe extends Command
|
490 |
+
{
|
491 |
+
/**
|
492 |
+
* {@inheritdoc}
|
493 |
+
*/
|
494 |
+
public function getId()
|
495 |
+
{
|
496 |
+
return 'UNSUBSCRIBE';
|
497 |
+
}
|
498 |
+
|
499 |
+
/**
|
500 |
+
* {@inheritdoc}
|
501 |
+
*/
|
502 |
+
protected function filterArguments(array $arguments)
|
503 |
+
{
|
504 |
+
return self::normalizeArguments($arguments);
|
505 |
+
}
|
506 |
+
}
|
507 |
+
|
508 |
+
/**
|
509 |
+
* @link http://redis.io/commands/info
|
510 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
511 |
+
*/
|
512 |
+
class ServerInfo extends Command
|
513 |
+
{
|
514 |
+
/**
|
515 |
+
* {@inheritdoc}
|
516 |
+
*/
|
517 |
+
public function getId()
|
518 |
+
{
|
519 |
+
return 'INFO';
|
520 |
+
}
|
521 |
+
|
522 |
+
/**
|
523 |
+
* {@inheritdoc}
|
524 |
+
*/
|
525 |
+
public function parseResponse($data)
|
526 |
+
{
|
527 |
+
$info = array();
|
528 |
+
$infoLines = preg_split('/\r?\n/', $data);
|
529 |
+
|
530 |
+
foreach ($infoLines as $row) {
|
531 |
+
if (strpos($row, ':') === false) {
|
532 |
+
continue;
|
533 |
+
}
|
534 |
+
|
535 |
+
list($k, $v) = $this->parseRow($row);
|
536 |
+
$info[$k] = $v;
|
537 |
+
}
|
538 |
+
|
539 |
+
return $info;
|
540 |
+
}
|
541 |
+
|
542 |
+
/**
|
543 |
+
* Parses a single row of the response and returns the key-value pair.
|
544 |
+
*
|
545 |
+
* @param string $row Single row of the response.
|
546 |
+
*
|
547 |
+
* @return array
|
548 |
+
*/
|
549 |
+
protected function parseRow($row)
|
550 |
+
{
|
551 |
+
list($k, $v) = explode(':', $row, 2);
|
552 |
+
|
553 |
+
if (preg_match('/^db\d+$/', $k)) {
|
554 |
+
$v = $this->parseDatabaseStats($v);
|
555 |
+
}
|
556 |
+
|
557 |
+
return array($k, $v);
|
558 |
+
}
|
559 |
+
|
560 |
+
/**
|
561 |
+
* Extracts the statistics of each logical DB from the string buffer.
|
562 |
+
*
|
563 |
+
* @param string $str Response buffer.
|
564 |
+
*
|
565 |
+
* @return array
|
566 |
+
*/
|
567 |
+
protected function parseDatabaseStats($str)
|
568 |
+
{
|
569 |
+
$db = array();
|
570 |
+
|
571 |
+
foreach (explode(',', $str) as $dbvar) {
|
572 |
+
list($dbvk, $dbvv) = explode('=', $dbvar);
|
573 |
+
$db[trim($dbvk)] = $dbvv;
|
574 |
+
}
|
575 |
+
|
576 |
+
return $db;
|
577 |
+
}
|
578 |
+
|
579 |
+
/**
|
580 |
+
* Parses the response and extracts the allocation statistics.
|
581 |
+
*
|
582 |
+
* @param string $str Response buffer.
|
583 |
+
*
|
584 |
+
* @return array
|
585 |
+
*/
|
586 |
+
protected function parseAllocationStats($str)
|
587 |
+
{
|
588 |
+
$stats = array();
|
589 |
+
|
590 |
+
foreach (explode(',', $str) as $kv) {
|
591 |
+
@list($size, $objects, $extra) = explode('=', $kv);
|
592 |
+
|
593 |
+
// hack to prevent incorrect values when parsing the >=256 key
|
594 |
+
if (isset($extra)) {
|
595 |
+
$size = ">=$objects";
|
596 |
+
$objects = $extra;
|
597 |
+
}
|
598 |
+
|
599 |
+
$stats[$size] = $objects;
|
600 |
+
}
|
601 |
+
|
602 |
+
return $stats;
|
603 |
+
}
|
604 |
+
}
|
605 |
+
|
606 |
+
/**
|
607 |
+
* @link http://redis.io/commands/evalsha
|
608 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
609 |
+
*/
|
610 |
+
class ServerEvalSHA extends ServerEval
|
611 |
+
{
|
612 |
+
/**
|
613 |
+
* {@inheritdoc}
|
614 |
+
*/
|
615 |
+
public function getId()
|
616 |
+
{
|
617 |
+
return 'EVALSHA';
|
618 |
+
}
|
619 |
+
|
620 |
+
/**
|
621 |
+
* Returns the SHA1 hash of the body of the script.
|
622 |
+
*
|
623 |
+
* @return string SHA1 hash.
|
624 |
+
*/
|
625 |
+
public function getScriptHash()
|
626 |
+
{
|
627 |
+
return $this->getArgument(0);
|
628 |
+
}
|
629 |
+
}
|
630 |
+
|
631 |
+
/**
|
632 |
+
* @link http://redis.io/commands/expire
|
633 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
634 |
+
*/
|
635 |
+
class KeyExpire extends Command
|
636 |
+
{
|
637 |
+
/**
|
638 |
+
* {@inheritdoc}
|
639 |
+
*/
|
640 |
+
public function getId()
|
641 |
+
{
|
642 |
+
return 'EXPIRE';
|
643 |
+
}
|
644 |
+
|
645 |
+
/**
|
646 |
+
* {@inheritdoc}
|
647 |
+
*/
|
648 |
+
public function parseResponse($data)
|
649 |
+
{
|
650 |
+
return (bool) $data;
|
651 |
+
}
|
652 |
+
}
|
653 |
+
|
654 |
+
/**
|
655 |
+
* @link http://redis.io/commands/subscribe
|
656 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
657 |
+
*/
|
658 |
+
class PubSubSubscribe extends Command
|
659 |
+
{
|
660 |
+
/**
|
661 |
+
* {@inheritdoc}
|
662 |
+
*/
|
663 |
+
public function getId()
|
664 |
+
{
|
665 |
+
return 'SUBSCRIBE';
|
666 |
+
}
|
667 |
+
|
668 |
+
/**
|
669 |
+
* {@inheritdoc}
|
670 |
+
*/
|
671 |
+
protected function filterArguments(array $arguments)
|
672 |
+
{
|
673 |
+
return self::normalizeArguments($arguments);
|
674 |
+
}
|
675 |
+
}
|
676 |
+
|
677 |
+
/**
|
678 |
+
* @link http://redis.io/commands/rpush
|
679 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
680 |
+
*/
|
681 |
+
class ListPushTail extends Command
|
682 |
+
{
|
683 |
+
/**
|
684 |
+
* {@inheritdoc}
|
685 |
+
*/
|
686 |
+
public function getId()
|
687 |
+
{
|
688 |
+
return 'RPUSH';
|
689 |
+
}
|
690 |
+
|
691 |
+
/**
|
692 |
+
* {@inheritdoc}
|
693 |
+
*/
|
694 |
+
protected function filterArguments(array $arguments)
|
695 |
+
{
|
696 |
+
return self::normalizeVariadic($arguments);
|
697 |
+
}
|
698 |
+
}
|
699 |
+
|
700 |
+
/**
|
701 |
+
* @link http://redis.io/commands/ttl
|
702 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
703 |
+
*/
|
704 |
+
class KeyTimeToLive extends Command
|
705 |
+
{
|
706 |
+
/**
|
707 |
+
* {@inheritdoc}
|
708 |
+
*/
|
709 |
+
public function getId()
|
710 |
+
{
|
711 |
+
return 'TTL';
|
712 |
+
}
|
713 |
+
}
|
714 |
+
|
715 |
+
/**
|
716 |
+
* @link http://redis.io/commands/zunionstore
|
717 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
718 |
+
*/
|
719 |
+
class ZSetUnionStore extends Command
|
720 |
+
{
|
721 |
+
/**
|
722 |
+
* {@inheritdoc}
|
723 |
+
*/
|
724 |
+
public function getId()
|
725 |
+
{
|
726 |
+
return 'ZUNIONSTORE';
|
727 |
+
}
|
728 |
+
|
729 |
+
/**
|
730 |
+
* {@inheritdoc}
|
731 |
+
*/
|
732 |
+
protected function filterArguments(array $arguments)
|
733 |
+
{
|
734 |
+
$options = array();
|
735 |
+
$argc = count($arguments);
|
736 |
+
|
737 |
+
if ($argc > 2 && is_array($arguments[$argc - 1])) {
|
738 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
739 |
+
}
|
740 |
+
|
741 |
+
if (is_array($arguments[1])) {
|
742 |
+
$arguments = array_merge(
|
743 |
+
array($arguments[0], count($arguments[1])),
|
744 |
+
$arguments[1]
|
745 |
+
);
|
746 |
+
}
|
747 |
+
|
748 |
+
return array_merge($arguments, $options);
|
749 |
+
}
|
750 |
+
|
751 |
+
/**
|
752 |
+
* Returns a list of options and modifiers compatible with Redis.
|
753 |
+
*
|
754 |
+
* @param array $options List of options.
|
755 |
+
*
|
756 |
+
* @return array
|
757 |
+
*/
|
758 |
+
private function prepareOptions($options)
|
759 |
+
{
|
760 |
+
$opts = array_change_key_case($options, CASE_UPPER);
|
761 |
+
$finalizedOpts = array();
|
762 |
+
|
763 |
+
if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
|
764 |
+
$finalizedOpts[] = 'WEIGHTS';
|
765 |
+
|
766 |
+
foreach ($opts['WEIGHTS'] as $weight) {
|
767 |
+
$finalizedOpts[] = $weight;
|
768 |
+
}
|
769 |
+
}
|
770 |
+
|
771 |
+
if (isset($opts['AGGREGATE'])) {
|
772 |
+
$finalizedOpts[] = 'AGGREGATE';
|
773 |
+
$finalizedOpts[] = $opts['AGGREGATE'];
|
774 |
+
}
|
775 |
+
|
776 |
+
return $finalizedOpts;
|
777 |
+
}
|
778 |
+
}
|
779 |
+
|
780 |
+
/**
|
781 |
+
* @link http://redis.io/commands/zrangebyscore
|
782 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
783 |
+
*/
|
784 |
+
class ZSetRangeByScore extends ZSetRange
|
785 |
+
{
|
786 |
+
/**
|
787 |
+
* {@inheritdoc}
|
788 |
+
*/
|
789 |
+
public function getId()
|
790 |
+
{
|
791 |
+
return 'ZRANGEBYSCORE';
|
792 |
+
}
|
793 |
+
|
794 |
+
/**
|
795 |
+
* {@inheritdoc}
|
796 |
+
*/
|
797 |
+
protected function prepareOptions($options)
|
798 |
+
{
|
799 |
+
$opts = array_change_key_case($options, CASE_UPPER);
|
800 |
+
$finalizedOpts = array();
|
801 |
+
|
802 |
+
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
|
803 |
+
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
|
804 |
+
|
805 |
+
$finalizedOpts[] = 'LIMIT';
|
806 |
+
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
|
807 |
+
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
|
808 |
+
}
|
809 |
+
|
810 |
+
return array_merge($finalizedOpts, parent::prepareOptions($options));
|
811 |
+
}
|
812 |
+
|
813 |
+
/**
|
814 |
+
* {@inheritdoc}
|
815 |
+
*/
|
816 |
+
protected function withScores()
|
817 |
+
{
|
818 |
+
$arguments = $this->getArguments();
|
819 |
+
|
820 |
+
for ($i = 3; $i < count($arguments); $i++) {
|
821 |
+
switch (strtoupper($arguments[$i])) {
|
822 |
+
case 'WITHSCORES':
|
823 |
+
return true;
|
824 |
+
|
825 |
+
case 'LIMIT':
|
826 |
+
$i += 2;
|
827 |
+
break;
|
828 |
+
}
|
829 |
+
}
|
830 |
+
|
831 |
+
return false;
|
832 |
+
}
|
833 |
+
}
|
834 |
+
|
835 |
+
/**
|
836 |
+
* @link http://redis.io/commands/zremrangebyrank
|
837 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
838 |
+
*/
|
839 |
+
class ZSetRemoveRangeByRank extends Command
|
840 |
+
{
|
841 |
+
/**
|
842 |
+
* {@inheritdoc}
|
843 |
+
*/
|
844 |
+
public function getId()
|
845 |
+
{
|
846 |
+
return 'ZREMRANGEBYRANK';
|
847 |
+
}
|
848 |
+
}
|
849 |
+
|
850 |
+
/**
|
851 |
+
* @link http://redis.io/commands/spop
|
852 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
853 |
+
*/
|
854 |
+
class SetPop extends Command
|
855 |
+
{
|
856 |
+
/**
|
857 |
+
* {@inheritdoc}
|
858 |
+
*/
|
859 |
+
public function getId()
|
860 |
+
{
|
861 |
+
return 'SPOP';
|
862 |
+
}
|
863 |
+
}
|
864 |
+
|
865 |
+
/**
|
866 |
+
* @link http://redis.io/commands/smove
|
867 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
868 |
+
*/
|
869 |
+
class SetMove extends Command
|
870 |
+
{
|
871 |
+
/**
|
872 |
+
* {@inheritdoc}
|
873 |
+
*/
|
874 |
+
public function getId()
|
875 |
+
{
|
876 |
+
return 'SMOVE';
|
877 |
+
}
|
878 |
+
|
879 |
+
/**
|
880 |
+
* {@inheritdoc}
|
881 |
+
*/
|
882 |
+
public function parseResponse($data)
|
883 |
+
{
|
884 |
+
return (bool) $data;
|
885 |
+
}
|
886 |
+
}
|
887 |
+
|
888 |
+
/**
|
889 |
+
* @link http://redis.io/commands/sismember
|
890 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
891 |
+
*/
|
892 |
+
class SetIsMember extends Command
|
893 |
+
{
|
894 |
+
/**
|
895 |
+
* {@inheritdoc}
|
896 |
+
*/
|
897 |
+
public function getId()
|
898 |
+
{
|
899 |
+
return 'SISMEMBER';
|
900 |
+
}
|
901 |
+
|
902 |
+
/**
|
903 |
+
* {@inheritdoc}
|
904 |
+
*/
|
905 |
+
public function parseResponse($data)
|
906 |
+
{
|
907 |
+
return (bool) $data;
|
908 |
+
}
|
909 |
+
}
|
910 |
+
|
911 |
+
/**
|
912 |
+
* @link http://redis.io/commands/smembers
|
913 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
914 |
+
*/
|
915 |
+
class SetMembers extends Command
|
916 |
+
{
|
917 |
+
/**
|
918 |
+
* {@inheritdoc}
|
919 |
+
*/
|
920 |
+
public function getId()
|
921 |
+
{
|
922 |
+
return 'SMEMBERS';
|
923 |
+
}
|
924 |
+
}
|
925 |
+
|
926 |
+
/**
|
927 |
+
* @link http://redis.io/commands/zremrangebyscore
|
928 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
929 |
+
*/
|
930 |
+
class ZSetRemoveRangeByScore extends Command
|
931 |
+
{
|
932 |
+
/**
|
933 |
+
* {@inheritdoc}
|
934 |
+
*/
|
935 |
+
public function getId()
|
936 |
+
{
|
937 |
+
return 'ZREMRANGEBYSCORE';
|
938 |
+
}
|
939 |
+
}
|
940 |
+
|
941 |
+
/**
|
942 |
+
* @link http://redis.io/commands/srandmember
|
943 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
944 |
+
*/
|
945 |
+
class SetRandomMember extends Command
|
946 |
+
{
|
947 |
+
/**
|
948 |
+
* {@inheritdoc}
|
949 |
+
*/
|
950 |
+
public function getId()
|
951 |
+
{
|
952 |
+
return 'SRANDMEMBER';
|
953 |
+
}
|
954 |
+
}
|
955 |
+
|
956 |
+
/**
|
957 |
+
* @link http://redis.io/commands/sscan
|
958 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
959 |
+
*/
|
960 |
+
class SetScan extends Command
|
961 |
+
{
|
962 |
+
/**
|
963 |
+
* {@inheritdoc}
|
964 |
+
*/
|
965 |
+
public function getId()
|
966 |
+
{
|
967 |
+
return 'SSCAN';
|
968 |
+
}
|
969 |
+
|
970 |
+
/**
|
971 |
+
* {@inheritdoc}
|
972 |
+
*/
|
973 |
+
protected function filterArguments(array $arguments)
|
974 |
+
{
|
975 |
+
if (count($arguments) === 3 && is_array($arguments[2])) {
|
976 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
977 |
+
$arguments = array_merge($arguments, $options);
|
978 |
+
}
|
979 |
+
|
980 |
+
return $arguments;
|
981 |
+
}
|
982 |
+
|
983 |
+
/**
|
984 |
+
* Returns a list of options and modifiers compatible with Redis.
|
985 |
+
*
|
986 |
+
* @param array $options List of options.
|
987 |
+
*
|
988 |
+
* @return array
|
989 |
+
*/
|
990 |
+
protected function prepareOptions($options)
|
991 |
+
{
|
992 |
+
$options = array_change_key_case($options, CASE_UPPER);
|
993 |
+
$normalized = array();
|
994 |
+
|
995 |
+
if (!empty($options['MATCH'])) {
|
996 |
+
$normalized[] = 'MATCH';
|
997 |
+
$normalized[] = $options['MATCH'];
|
998 |
+
}
|
999 |
+
|
1000 |
+
if (!empty($options['COUNT'])) {
|
1001 |
+
$normalized[] = 'COUNT';
|
1002 |
+
$normalized[] = $options['COUNT'];
|
1003 |
+
}
|
1004 |
+
|
1005 |
+
return $normalized;
|
1006 |
+
}
|
1007 |
+
}
|
1008 |
+
|
1009 |
+
/**
|
1010 |
+
* @link http://redis.io/commands/zremrangebylex
|
1011 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1012 |
+
*/
|
1013 |
+
class ZSetRemoveRangeByLex extends Command
|
1014 |
+
{
|
1015 |
+
/**
|
1016 |
+
* {@inheritdoc}
|
1017 |
+
*/
|
1018 |
+
public function getId()
|
1019 |
+
{
|
1020 |
+
return 'ZREMRANGEBYLEX';
|
1021 |
+
}
|
1022 |
+
}
|
1023 |
+
|
1024 |
+
/**
|
1025 |
+
* @link http://redis.io/commands/bitop
|
1026 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1027 |
+
*/
|
1028 |
+
class StringBitOp extends Command
|
1029 |
+
{
|
1030 |
+
/**
|
1031 |
+
* {@inheritdoc}
|
1032 |
+
*/
|
1033 |
+
public function getId()
|
1034 |
+
{
|
1035 |
+
return 'BITOP';
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
/**
|
1039 |
+
* {@inheritdoc}
|
1040 |
+
*/
|
1041 |
+
protected function filterArguments(array $arguments)
|
1042 |
+
{
|
1043 |
+
if (count($arguments) === 3 && is_array($arguments[2])) {
|
1044 |
+
list($operation, $destination, ) = $arguments;
|
1045 |
+
$arguments = $arguments[2];
|
1046 |
+
array_unshift($arguments, $operation, $destination);
|
1047 |
+
}
|
1048 |
+
|
1049 |
+
return $arguments;
|
1050 |
+
}
|
1051 |
+
}
|
1052 |
+
|
1053 |
+
/**
|
1054 |
+
* @link http://redis.io/commands/bitcount
|
1055 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1056 |
+
*/
|
1057 |
+
class StringBitCount extends Command
|
1058 |
+
{
|
1059 |
+
/**
|
1060 |
+
* {@inheritdoc}
|
1061 |
+
*/
|
1062 |
+
public function getId()
|
1063 |
+
{
|
1064 |
+
return 'BITCOUNT';
|
1065 |
+
}
|
1066 |
+
}
|
1067 |
+
|
1068 |
+
/**
|
1069 |
+
* @link http://redis.io/commands/append
|
1070 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1071 |
+
*/
|
1072 |
+
class StringAppend extends Command
|
1073 |
+
{
|
1074 |
+
/**
|
1075 |
+
* {@inheritdoc}
|
1076 |
+
*/
|
1077 |
+
public function getId()
|
1078 |
+
{
|
1079 |
+
return 'APPEND';
|
1080 |
+
}
|
1081 |
+
}
|
1082 |
+
|
1083 |
+
/**
|
1084 |
+
* @link http://redis.io/commands/sunion
|
1085 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1086 |
+
*/
|
1087 |
+
class SetUnion extends SetIntersection
|
1088 |
+
{
|
1089 |
+
/**
|
1090 |
+
* {@inheritdoc}
|
1091 |
+
*/
|
1092 |
+
public function getId()
|
1093 |
+
{
|
1094 |
+
return 'SUNION';
|
1095 |
+
}
|
1096 |
+
}
|
1097 |
+
|
1098 |
+
/**
|
1099 |
+
* @link http://redis.io/commands/sunionstore
|
1100 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1101 |
+
*/
|
1102 |
+
class SetUnionStore extends SetIntersectionStore
|
1103 |
+
{
|
1104 |
+
/**
|
1105 |
+
* {@inheritdoc}
|
1106 |
+
*/
|
1107 |
+
public function getId()
|
1108 |
+
{
|
1109 |
+
return 'SUNIONSTORE';
|
1110 |
+
}
|
1111 |
+
}
|
1112 |
+
|
1113 |
+
/**
|
1114 |
+
* @link http://redis.io/commands/srem
|
1115 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1116 |
+
*/
|
1117 |
+
class SetRemove extends Command
|
1118 |
+
{
|
1119 |
+
/**
|
1120 |
+
* {@inheritdoc}
|
1121 |
+
*/
|
1122 |
+
public function getId()
|
1123 |
+
{
|
1124 |
+
return 'SREM';
|
1125 |
+
}
|
1126 |
+
|
1127 |
+
/**
|
1128 |
+
* {@inheritdoc}
|
1129 |
+
*/
|
1130 |
+
protected function filterArguments(array $arguments)
|
1131 |
+
{
|
1132 |
+
return self::normalizeVariadic($arguments);
|
1133 |
+
}
|
1134 |
+
}
|
1135 |
+
|
1136 |
+
/**
|
1137 |
+
* @link http://redis.io/commands/zrevrange
|
1138 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1139 |
+
*/
|
1140 |
+
class ZSetReverseRange extends ZSetRange
|
1141 |
+
{
|
1142 |
+
/**
|
1143 |
+
* {@inheritdoc}
|
1144 |
+
*/
|
1145 |
+
public function getId()
|
1146 |
+
{
|
1147 |
+
return 'ZREVRANGE';
|
1148 |
+
}
|
1149 |
+
}
|
1150 |
+
|
1151 |
+
/**
|
1152 |
+
* @link http://redis.io/commands/slowlog
|
1153 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1154 |
+
*/
|
1155 |
+
class ServerSlowlog extends Command
|
1156 |
+
{
|
1157 |
+
/**
|
1158 |
+
* {@inheritdoc}
|
1159 |
+
*/
|
1160 |
+
public function getId()
|
1161 |
+
{
|
1162 |
+
return 'SLOWLOG';
|
1163 |
+
}
|
1164 |
+
|
1165 |
+
/**
|
1166 |
+
* {@inheritdoc}
|
1167 |
+
*/
|
1168 |
+
public function parseResponse($data)
|
1169 |
+
{
|
1170 |
+
if (is_array($data)) {
|
1171 |
+
$log = array();
|
1172 |
+
|
1173 |
+
foreach ($data as $index => $entry) {
|
1174 |
+
$log[$index] = array(
|
1175 |
+
'id' => $entry[0],
|
1176 |
+
'timestamp' => $entry[1],
|
1177 |
+
'duration' => $entry[2],
|
1178 |
+
'command' => $entry[3],
|
1179 |
+
);
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
return $log;
|
1183 |
+
}
|
1184 |
+
|
1185 |
+
return $data;
|
1186 |
+
}
|
1187 |
+
}
|
1188 |
+
|
1189 |
+
/**
|
1190 |
+
* @link http://redis.io/commands/zscore
|
1191 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1192 |
+
*/
|
1193 |
+
class ZSetScore extends Command
|
1194 |
+
{
|
1195 |
+
/**
|
1196 |
+
* {@inheritdoc}
|
1197 |
+
*/
|
1198 |
+
public function getId()
|
1199 |
+
{
|
1200 |
+
return 'ZSCORE';
|
1201 |
+
}
|
1202 |
+
}
|
1203 |
+
|
1204 |
+
/**
|
1205 |
+
* @link http://redis.io/commands/slaveof
|
1206 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1207 |
+
*/
|
1208 |
+
class ServerSlaveOf extends Command
|
1209 |
+
{
|
1210 |
+
/**
|
1211 |
+
* {@inheritdoc}
|
1212 |
+
*/
|
1213 |
+
public function getId()
|
1214 |
+
{
|
1215 |
+
return 'SLAVEOF';
|
1216 |
+
}
|
1217 |
+
|
1218 |
+
/**
|
1219 |
+
* {@inheritdoc}
|
1220 |
+
*/
|
1221 |
+
protected function filterArguments(array $arguments)
|
1222 |
+
{
|
1223 |
+
if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
|
1224 |
+
return array('NO', 'ONE');
|
1225 |
+
}
|
1226 |
+
|
1227 |
+
return $arguments;
|
1228 |
+
}
|
1229 |
+
}
|
1230 |
+
|
1231 |
+
/**
|
1232 |
+
* @link http://redis.io/commands/shutdown
|
1233 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1234 |
+
*/
|
1235 |
+
class ServerShutdown extends Command
|
1236 |
+
{
|
1237 |
+
/**
|
1238 |
+
* {@inheritdoc}
|
1239 |
+
*/
|
1240 |
+
public function getId()
|
1241 |
+
{
|
1242 |
+
return 'SHUTDOWN';
|
1243 |
+
}
|
1244 |
+
}
|
1245 |
+
|
1246 |
+
/**
|
1247 |
+
* @link http://redis.io/commands/script
|
1248 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1249 |
+
*/
|
1250 |
+
class ServerScript extends Command
|
1251 |
+
{
|
1252 |
+
/**
|
1253 |
+
* {@inheritdoc}
|
1254 |
+
*/
|
1255 |
+
public function getId()
|
1256 |
+
{
|
1257 |
+
return 'SCRIPT';
|
1258 |
+
}
|
1259 |
+
}
|
1260 |
+
|
1261 |
+
/**
|
1262 |
+
* @link http://redis.io/topics/sentinel
|
1263 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1264 |
+
*/
|
1265 |
+
class ServerSentinel extends Command
|
1266 |
+
{
|
1267 |
+
/**
|
1268 |
+
* {@inheritdoc}
|
1269 |
+
*/
|
1270 |
+
public function getId()
|
1271 |
+
{
|
1272 |
+
return 'SENTINEL';
|
1273 |
+
}
|
1274 |
+
|
1275 |
+
/**
|
1276 |
+
* {@inheritdoc}
|
1277 |
+
*/
|
1278 |
+
public function parseResponse($data)
|
1279 |
+
{
|
1280 |
+
switch (strtolower($this->getArgument(0))) {
|
1281 |
+
case 'masters':
|
1282 |
+
case 'slaves':
|
1283 |
+
return self::processMastersOrSlaves($data);
|
1284 |
+
|
1285 |
+
default:
|
1286 |
+
return $data;
|
1287 |
+
}
|
1288 |
+
}
|
1289 |
+
|
1290 |
+
/**
|
1291 |
+
* Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES.
|
1292 |
+
*
|
1293 |
+
* @param array $servers List of Redis servers.
|
1294 |
+
*
|
1295 |
+
* @return array
|
1296 |
+
*/
|
1297 |
+
protected static function processMastersOrSlaves(array $servers)
|
1298 |
+
{
|
1299 |
+
foreach ($servers as $idx => $node) {
|
1300 |
+
$processed = array();
|
1301 |
+
$count = count($node);
|
1302 |
+
|
1303 |
+
for ($i = 0; $i < $count; $i++) {
|
1304 |
+
$processed[$node[$i]] = $node[++$i];
|
1305 |
+
}
|
1306 |
+
|
1307 |
+
$servers[$idx] = $processed;
|
1308 |
+
}
|
1309 |
+
|
1310 |
+
return $servers;
|
1311 |
+
}
|
1312 |
+
}
|
1313 |
+
|
1314 |
+
/**
|
1315 |
+
* @link http://redis.io/commands/zscan
|
1316 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1317 |
+
*/
|
1318 |
+
class ZSetScan extends Command
|
1319 |
+
{
|
1320 |
+
/**
|
1321 |
+
* {@inheritdoc}
|
1322 |
+
*/
|
1323 |
+
public function getId()
|
1324 |
+
{
|
1325 |
+
return 'ZSCAN';
|
1326 |
+
}
|
1327 |
+
|
1328 |
+
/**
|
1329 |
+
* {@inheritdoc}
|
1330 |
+
*/
|
1331 |
+
protected function filterArguments(array $arguments)
|
1332 |
+
{
|
1333 |
+
if (count($arguments) === 3 && is_array($arguments[2])) {
|
1334 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
1335 |
+
$arguments = array_merge($arguments, $options);
|
1336 |
+
}
|
1337 |
+
|
1338 |
+
return $arguments;
|
1339 |
+
}
|
1340 |
+
|
1341 |
+
/**
|
1342 |
+
* Returns a list of options and modifiers compatible with Redis.
|
1343 |
+
*
|
1344 |
+
* @param array $options List of options.
|
1345 |
+
*
|
1346 |
+
* @return array
|
1347 |
+
*/
|
1348 |
+
protected function prepareOptions($options)
|
1349 |
+
{
|
1350 |
+
$options = array_change_key_case($options, CASE_UPPER);
|
1351 |
+
$normalized = array();
|
1352 |
+
|
1353 |
+
if (!empty($options['MATCH'])) {
|
1354 |
+
$normalized[] = 'MATCH';
|
1355 |
+
$normalized[] = $options['MATCH'];
|
1356 |
+
}
|
1357 |
+
|
1358 |
+
if (!empty($options['COUNT'])) {
|
1359 |
+
$normalized[] = 'COUNT';
|
1360 |
+
$normalized[] = $options['COUNT'];
|
1361 |
+
}
|
1362 |
+
|
1363 |
+
return $normalized;
|
1364 |
+
}
|
1365 |
+
|
1366 |
+
/**
|
1367 |
+
* {@inheritdoc}
|
1368 |
+
*/
|
1369 |
+
public function parseResponse($data)
|
1370 |
+
{
|
1371 |
+
if (is_array($data)) {
|
1372 |
+
$members = $data[1];
|
1373 |
+
$result = array();
|
1374 |
+
|
1375 |
+
for ($i = 0; $i < count($members); $i++) {
|
1376 |
+
$result[$members[$i]] = (float) $members[++$i];
|
1377 |
+
}
|
1378 |
+
|
1379 |
+
$data[1] = $result;
|
1380 |
+
}
|
1381 |
+
|
1382 |
+
return $data;
|
1383 |
+
}
|
1384 |
+
}
|
1385 |
+
|
1386 |
+
/**
|
1387 |
+
* @link http://redis.io/commands/zrevrank
|
1388 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1389 |
+
*/
|
1390 |
+
class ZSetReverseRank extends Command
|
1391 |
+
{
|
1392 |
+
/**
|
1393 |
+
* {@inheritdoc}
|
1394 |
+
*/
|
1395 |
+
public function getId()
|
1396 |
+
{
|
1397 |
+
return 'ZREVRANK';
|
1398 |
+
}
|
1399 |
+
}
|
1400 |
+
|
1401 |
+
/**
|
1402 |
+
* @link http://redis.io/commands/sdiffstore
|
1403 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1404 |
+
*/
|
1405 |
+
class SetDifferenceStore extends SetIntersectionStore
|
1406 |
+
{
|
1407 |
+
/**
|
1408 |
+
* {@inheritdoc}
|
1409 |
+
*/
|
1410 |
+
public function getId()
|
1411 |
+
{
|
1412 |
+
return 'SDIFFSTORE';
|
1413 |
+
}
|
1414 |
+
}
|
1415 |
+
|
1416 |
+
/**
|
1417 |
+
* @link http://redis.io/commands/zrevrangebyscore
|
1418 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1419 |
+
*/
|
1420 |
+
class ZSetReverseRangeByScore extends ZSetRangeByScore
|
1421 |
+
{
|
1422 |
+
/**
|
1423 |
+
* {@inheritdoc}
|
1424 |
+
*/
|
1425 |
+
public function getId()
|
1426 |
+
{
|
1427 |
+
return 'ZREVRANGEBYSCORE';
|
1428 |
+
}
|
1429 |
+
}
|
1430 |
+
|
1431 |
+
/**
|
1432 |
+
* @link http://redis.io/commands/sdiff
|
1433 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1434 |
+
*/
|
1435 |
+
class SetDifference extends SetIntersection
|
1436 |
+
{
|
1437 |
+
/**
|
1438 |
+
* {@inheritdoc}
|
1439 |
+
*/
|
1440 |
+
public function getId()
|
1441 |
+
{
|
1442 |
+
return 'SDIFF';
|
1443 |
+
}
|
1444 |
+
}
|
1445 |
+
|
1446 |
+
/**
|
1447 |
+
* @link http://redis.io/commands/scard
|
1448 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1449 |
+
*/
|
1450 |
+
class SetCardinality extends Command
|
1451 |
+
{
|
1452 |
+
/**
|
1453 |
+
* {@inheritdoc}
|
1454 |
+
*/
|
1455 |
+
public function getId()
|
1456 |
+
{
|
1457 |
+
return 'SCARD';
|
1458 |
+
}
|
1459 |
+
}
|
1460 |
+
|
1461 |
+
/**
|
1462 |
+
* @link http://redis.io/commands/time
|
1463 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1464 |
+
*/
|
1465 |
+
class ServerTime extends Command
|
1466 |
+
{
|
1467 |
+
/**
|
1468 |
+
* {@inheritdoc}
|
1469 |
+
*/
|
1470 |
+
public function getId()
|
1471 |
+
{
|
1472 |
+
return 'TIME';
|
1473 |
+
}
|
1474 |
+
}
|
1475 |
+
|
1476 |
+
/**
|
1477 |
+
* @link http://redis.io/commands/sadd
|
1478 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1479 |
+
*/
|
1480 |
+
class SetAdd extends Command
|
1481 |
+
{
|
1482 |
+
/**
|
1483 |
+
* {@inheritdoc}
|
1484 |
+
*/
|
1485 |
+
public function getId()
|
1486 |
+
{
|
1487 |
+
return 'SADD';
|
1488 |
+
}
|
1489 |
+
|
1490 |
+
/**
|
1491 |
+
* {@inheritdoc}
|
1492 |
+
*/
|
1493 |
+
protected function filterArguments(array $arguments)
|
1494 |
+
{
|
1495 |
+
return self::normalizeVariadic($arguments);
|
1496 |
+
}
|
1497 |
+
}
|
1498 |
+
|
1499 |
+
/**
|
1500 |
+
* @link http://redis.io/commands/bitpos
|
1501 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1502 |
+
*/
|
1503 |
+
class StringBitPos extends Command
|
1504 |
+
{
|
1505 |
+
/**
|
1506 |
+
* {@inheritdoc}
|
1507 |
+
*/
|
1508 |
+
public function getId()
|
1509 |
+
{
|
1510 |
+
return 'BITPOS';
|
1511 |
+
}
|
1512 |
+
}
|
1513 |
+
|
1514 |
+
/**
|
1515 |
+
* @link http://redis.io/commands/decrby
|
1516 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1517 |
+
*/
|
1518 |
+
class StringDecrementBy extends Command
|
1519 |
+
{
|
1520 |
+
/**
|
1521 |
+
* {@inheritdoc}
|
1522 |
+
*/
|
1523 |
+
public function getId()
|
1524 |
+
{
|
1525 |
+
return 'DECRBY';
|
1526 |
+
}
|
1527 |
+
}
|
1528 |
+
|
1529 |
+
/**
|
1530 |
+
* @link http://redis.io/commands/substr
|
1531 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1532 |
+
*/
|
1533 |
+
class StringSubstr extends Command
|
1534 |
+
{
|
1535 |
+
/**
|
1536 |
+
* {@inheritdoc}
|
1537 |
+
*/
|
1538 |
+
public function getId()
|
1539 |
+
{
|
1540 |
+
return 'SUBSTR';
|
1541 |
+
}
|
1542 |
+
}
|
1543 |
+
|
1544 |
+
/**
|
1545 |
+
* @link http://redis.io/commands/zlexcount
|
1546 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1547 |
+
*/
|
1548 |
+
class ZSetLexCount extends Command
|
1549 |
+
{
|
1550 |
+
/**
|
1551 |
+
* {@inheritdoc}
|
1552 |
+
*/
|
1553 |
+
public function getId()
|
1554 |
+
{
|
1555 |
+
return 'ZLEXCOUNT';
|
1556 |
+
}
|
1557 |
+
}
|
1558 |
+
|
1559 |
+
/**
|
1560 |
+
* @link http://redis.io/commands/zinterstore
|
1561 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1562 |
+
*/
|
1563 |
+
class ZSetIntersectionStore extends ZSetUnionStore
|
1564 |
+
{
|
1565 |
+
/**
|
1566 |
+
* {@inheritdoc}
|
1567 |
+
*/
|
1568 |
+
public function getId()
|
1569 |
+
{
|
1570 |
+
return 'ZINTERSTORE';
|
1571 |
+
}
|
1572 |
+
}
|
1573 |
+
|
1574 |
+
/**
|
1575 |
+
* @link http://redis.io/commands/strlen
|
1576 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1577 |
+
*/
|
1578 |
+
class StringStrlen extends Command
|
1579 |
+
{
|
1580 |
+
/**
|
1581 |
+
* {@inheritdoc}
|
1582 |
+
*/
|
1583 |
+
public function getId()
|
1584 |
+
{
|
1585 |
+
return 'STRLEN';
|
1586 |
+
}
|
1587 |
+
}
|
1588 |
+
|
1589 |
+
/**
|
1590 |
+
* @link http://redis.io/commands/setrange
|
1591 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1592 |
+
*/
|
1593 |
+
class StringSetRange extends Command
|
1594 |
+
{
|
1595 |
+
/**
|
1596 |
+
* {@inheritdoc}
|
1597 |
+
*/
|
1598 |
+
public function getId()
|
1599 |
+
{
|
1600 |
+
return 'SETRANGE';
|
1601 |
+
}
|
1602 |
+
}
|
1603 |
+
|
1604 |
+
/**
|
1605 |
+
* @link http://redis.io/commands/msetnx
|
1606 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1607 |
+
*/
|
1608 |
+
class StringSetMultiplePreserve extends StringSetMultiple
|
1609 |
+
{
|
1610 |
+
/**
|
1611 |
+
* {@inheritdoc}
|
1612 |
+
*/
|
1613 |
+
public function getId()
|
1614 |
+
{
|
1615 |
+
return 'MSETNX';
|
1616 |
+
}
|
1617 |
+
|
1618 |
+
/**
|
1619 |
+
* {@inheritdoc}
|
1620 |
+
*/
|
1621 |
+
public function parseResponse($data)
|
1622 |
+
{
|
1623 |
+
return (bool) $data;
|
1624 |
+
}
|
1625 |
+
}
|
1626 |
+
|
1627 |
+
/**
|
1628 |
+
* @link http://redis.io/commands/setnx
|
1629 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1630 |
+
*/
|
1631 |
+
class StringSetPreserve extends Command
|
1632 |
+
{
|
1633 |
+
/**
|
1634 |
+
* {@inheritdoc}
|
1635 |
+
*/
|
1636 |
+
public function getId()
|
1637 |
+
{
|
1638 |
+
return 'SETNX';
|
1639 |
+
}
|
1640 |
+
|
1641 |
+
/**
|
1642 |
+
* {@inheritdoc}
|
1643 |
+
*/
|
1644 |
+
public function parseResponse($data)
|
1645 |
+
{
|
1646 |
+
return (bool) $data;
|
1647 |
+
}
|
1648 |
+
}
|
1649 |
+
|
1650 |
+
/**
|
1651 |
+
* @link http://redis.io/commands/discard
|
1652 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1653 |
+
*/
|
1654 |
+
class TransactionDiscard extends Command
|
1655 |
+
{
|
1656 |
+
/**
|
1657 |
+
* {@inheritdoc}
|
1658 |
+
*/
|
1659 |
+
public function getId()
|
1660 |
+
{
|
1661 |
+
return 'DISCARD';
|
1662 |
+
}
|
1663 |
+
}
|
1664 |
+
|
1665 |
+
/**
|
1666 |
+
* @link http://redis.io/commands/exec
|
1667 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1668 |
+
*/
|
1669 |
+
class TransactionExec extends Command
|
1670 |
+
{
|
1671 |
+
/**
|
1672 |
+
* {@inheritdoc}
|
1673 |
+
*/
|
1674 |
+
public function getId()
|
1675 |
+
{
|
1676 |
+
return 'EXEC';
|
1677 |
+
}
|
1678 |
+
}
|
1679 |
+
|
1680 |
+
/**
|
1681 |
+
* @link http://redis.io/commands/zcard
|
1682 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1683 |
+
*/
|
1684 |
+
class ZSetCardinality extends Command
|
1685 |
+
{
|
1686 |
+
/**
|
1687 |
+
* {@inheritdoc}
|
1688 |
+
*/
|
1689 |
+
public function getId()
|
1690 |
+
{
|
1691 |
+
return 'ZCARD';
|
1692 |
+
}
|
1693 |
+
}
|
1694 |
+
|
1695 |
+
/**
|
1696 |
+
* @link http://redis.io/commands/zcount
|
1697 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1698 |
+
*/
|
1699 |
+
class ZSetCount extends Command
|
1700 |
+
{
|
1701 |
+
/**
|
1702 |
+
* {@inheritdoc}
|
1703 |
+
*/
|
1704 |
+
public function getId()
|
1705 |
+
{
|
1706 |
+
return 'ZCOUNT';
|
1707 |
+
}
|
1708 |
+
}
|
1709 |
+
|
1710 |
+
/**
|
1711 |
+
* @link http://redis.io/commands/zadd
|
1712 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1713 |
+
*/
|
1714 |
+
class ZSetAdd extends Command
|
1715 |
+
{
|
1716 |
+
/**
|
1717 |
+
* {@inheritdoc}
|
1718 |
+
*/
|
1719 |
+
public function getId()
|
1720 |
+
{
|
1721 |
+
return 'ZADD';
|
1722 |
+
}
|
1723 |
+
|
1724 |
+
/**
|
1725 |
+
* {@inheritdoc}
|
1726 |
+
*/
|
1727 |
+
protected function filterArguments(array $arguments)
|
1728 |
+
{
|
1729 |
+
if (count($arguments) === 2 && is_array($arguments[1])) {
|
1730 |
+
$flattened = array($arguments[0]);
|
1731 |
+
|
1732 |
+
foreach ($arguments[1] as $member => $score) {
|
1733 |
+
$flattened[] = $score;
|
1734 |
+
$flattened[] = $member;
|
1735 |
+
}
|
1736 |
+
|
1737 |
+
return $flattened;
|
1738 |
+
}
|
1739 |
+
|
1740 |
+
return $arguments;
|
1741 |
+
}
|
1742 |
+
}
|
1743 |
+
|
1744 |
+
/**
|
1745 |
+
* @link http://redis.io/commands/watch
|
1746 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1747 |
+
*/
|
1748 |
+
class TransactionWatch extends Command
|
1749 |
+
{
|
1750 |
+
/**
|
1751 |
+
* {@inheritdoc}
|
1752 |
+
*/
|
1753 |
+
public function getId()
|
1754 |
+
{
|
1755 |
+
return 'WATCH';
|
1756 |
+
}
|
1757 |
+
|
1758 |
+
/**
|
1759 |
+
* {@inheritdoc}
|
1760 |
+
*/
|
1761 |
+
protected function filterArguments(array $arguments)
|
1762 |
+
{
|
1763 |
+
if (isset($arguments[0]) && is_array($arguments[0])) {
|
1764 |
+
return $arguments[0];
|
1765 |
+
}
|
1766 |
+
|
1767 |
+
return $arguments;
|
1768 |
+
}
|
1769 |
+
}
|
1770 |
+
|
1771 |
+
/**
|
1772 |
+
* @link http://redis.io/commands/multi
|
1773 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1774 |
+
*/
|
1775 |
+
class TransactionMulti extends Command
|
1776 |
+
{
|
1777 |
+
/**
|
1778 |
+
* {@inheritdoc}
|
1779 |
+
*/
|
1780 |
+
public function getId()
|
1781 |
+
{
|
1782 |
+
return 'MULTI';
|
1783 |
+
}
|
1784 |
+
}
|
1785 |
+
|
1786 |
+
/**
|
1787 |
+
* @link http://redis.io/commands/unwatch
|
1788 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1789 |
+
*/
|
1790 |
+
class TransactionUnwatch extends Command
|
1791 |
+
{
|
1792 |
+
/**
|
1793 |
+
* {@inheritdoc}
|
1794 |
+
*/
|
1795 |
+
public function getId()
|
1796 |
+
{
|
1797 |
+
return 'UNWATCH';
|
1798 |
+
}
|
1799 |
+
}
|
1800 |
+
|
1801 |
+
/**
|
1802 |
+
* @link http://redis.io/commands/zrangebylex
|
1803 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1804 |
+
*/
|
1805 |
+
class ZSetRangeByLex extends ZSetRange
|
1806 |
+
{
|
1807 |
+
/**
|
1808 |
+
* {@inheritdoc}
|
1809 |
+
*/
|
1810 |
+
public function getId()
|
1811 |
+
{
|
1812 |
+
return 'ZRANGEBYLEX';
|
1813 |
+
}
|
1814 |
+
|
1815 |
+
/**
|
1816 |
+
* {@inheritdoc}
|
1817 |
+
*/
|
1818 |
+
protected function prepareOptions($options)
|
1819 |
+
{
|
1820 |
+
$opts = array_change_key_case($options, CASE_UPPER);
|
1821 |
+
$finalizedOpts = array();
|
1822 |
+
|
1823 |
+
if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
|
1824 |
+
$limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
|
1825 |
+
|
1826 |
+
$finalizedOpts[] = 'LIMIT';
|
1827 |
+
$finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
|
1828 |
+
$finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
|
1829 |
+
}
|
1830 |
+
|
1831 |
+
return $finalizedOpts;
|
1832 |
+
}
|
1833 |
+
|
1834 |
+
/**
|
1835 |
+
* {@inheritdoc}
|
1836 |
+
*/
|
1837 |
+
protected function withScores()
|
1838 |
+
{
|
1839 |
+
return false;
|
1840 |
+
}
|
1841 |
+
}
|
1842 |
+
|
1843 |
+
/**
|
1844 |
+
* @link http://redis.io/commands/zrank
|
1845 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1846 |
+
*/
|
1847 |
+
class ZSetRank extends Command
|
1848 |
+
{
|
1849 |
+
/**
|
1850 |
+
* {@inheritdoc}
|
1851 |
+
*/
|
1852 |
+
public function getId()
|
1853 |
+
{
|
1854 |
+
return 'ZRANK';
|
1855 |
+
}
|
1856 |
+
}
|
1857 |
+
|
1858 |
+
/**
|
1859 |
+
* @link http://redis.io/commands/mget
|
1860 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1861 |
+
*/
|
1862 |
+
class StringGetMultiple extends Command
|
1863 |
+
{
|
1864 |
+
/**
|
1865 |
+
* {@inheritdoc}
|
1866 |
+
*/
|
1867 |
+
public function getId()
|
1868 |
+
{
|
1869 |
+
return 'MGET';
|
1870 |
+
}
|
1871 |
+
|
1872 |
+
/**
|
1873 |
+
* {@inheritdoc}
|
1874 |
+
*/
|
1875 |
+
protected function filterArguments(array $arguments)
|
1876 |
+
{
|
1877 |
+
return self::normalizeArguments($arguments);
|
1878 |
+
}
|
1879 |
+
}
|
1880 |
+
|
1881 |
+
/**
|
1882 |
+
* @link http://redis.io/commands/getrange
|
1883 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1884 |
+
*/
|
1885 |
+
class StringGetRange extends Command
|
1886 |
+
{
|
1887 |
+
/**
|
1888 |
+
* {@inheritdoc}
|
1889 |
+
*/
|
1890 |
+
public function getId()
|
1891 |
+
{
|
1892 |
+
return 'GETRANGE';
|
1893 |
+
}
|
1894 |
+
}
|
1895 |
+
|
1896 |
+
/**
|
1897 |
+
* @link http://redis.io/commands/zrem
|
1898 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1899 |
+
*/
|
1900 |
+
class ZSetRemove extends Command
|
1901 |
+
{
|
1902 |
+
/**
|
1903 |
+
* {@inheritdoc}
|
1904 |
+
*/
|
1905 |
+
public function getId()
|
1906 |
+
{
|
1907 |
+
return 'ZREM';
|
1908 |
+
}
|
1909 |
+
|
1910 |
+
/**
|
1911 |
+
* {@inheritdoc}
|
1912 |
+
*/
|
1913 |
+
protected function filterArguments(array $arguments)
|
1914 |
+
{
|
1915 |
+
return self::normalizeVariadic($arguments);
|
1916 |
+
}
|
1917 |
+
}
|
1918 |
+
|
1919 |
+
/**
|
1920 |
+
* @link http://redis.io/commands/getbit
|
1921 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1922 |
+
*/
|
1923 |
+
class StringGetBit extends Command
|
1924 |
+
{
|
1925 |
+
/**
|
1926 |
+
* {@inheritdoc}
|
1927 |
+
*/
|
1928 |
+
public function getId()
|
1929 |
+
{
|
1930 |
+
return 'GETBIT';
|
1931 |
+
}
|
1932 |
+
}
|
1933 |
+
|
1934 |
+
/**
|
1935 |
+
* @link http://redis.io/commands/zincrby
|
1936 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1937 |
+
*/
|
1938 |
+
class ZSetIncrementBy extends Command
|
1939 |
+
{
|
1940 |
+
/**
|
1941 |
+
* {@inheritdoc}
|
1942 |
+
*/
|
1943 |
+
public function getId()
|
1944 |
+
{
|
1945 |
+
return 'ZINCRBY';
|
1946 |
+
}
|
1947 |
+
}
|
1948 |
+
|
1949 |
+
/**
|
1950 |
+
* @link http://redis.io/commands/get
|
1951 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1952 |
+
*/
|
1953 |
+
class StringGet extends Command
|
1954 |
+
{
|
1955 |
+
/**
|
1956 |
+
* {@inheritdoc}
|
1957 |
+
*/
|
1958 |
+
public function getId()
|
1959 |
+
{
|
1960 |
+
return 'GET';
|
1961 |
+
}
|
1962 |
+
}
|
1963 |
+
|
1964 |
+
/**
|
1965 |
+
* @link http://redis.io/commands/getset
|
1966 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1967 |
+
*/
|
1968 |
+
class StringGetSet extends Command
|
1969 |
+
{
|
1970 |
+
/**
|
1971 |
+
* {@inheritdoc}
|
1972 |
+
*/
|
1973 |
+
public function getId()
|
1974 |
+
{
|
1975 |
+
return 'GETSET';
|
1976 |
+
}
|
1977 |
+
}
|
1978 |
+
|
1979 |
+
/**
|
1980 |
+
* @link http://redis.io/commands/incr
|
1981 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1982 |
+
*/
|
1983 |
+
class StringIncrement extends Command
|
1984 |
+
{
|
1985 |
+
/**
|
1986 |
+
* {@inheritdoc}
|
1987 |
+
*/
|
1988 |
+
public function getId()
|
1989 |
+
{
|
1990 |
+
return 'INCR';
|
1991 |
+
}
|
1992 |
+
}
|
1993 |
+
|
1994 |
+
/**
|
1995 |
+
* @link http://redis.io/commands/set
|
1996 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
1997 |
+
*/
|
1998 |
+
class StringSet extends Command
|
1999 |
+
{
|
2000 |
+
/**
|
2001 |
+
* {@inheritdoc}
|
2002 |
+
*/
|
2003 |
+
public function getId()
|
2004 |
+
{
|
2005 |
+
return 'SET';
|
2006 |
+
}
|
2007 |
+
}
|
2008 |
+
|
2009 |
+
/**
|
2010 |
+
* @link http://redis.io/commands/setbit
|
2011 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2012 |
+
*/
|
2013 |
+
class StringSetBit extends Command
|
2014 |
+
{
|
2015 |
+
/**
|
2016 |
+
* {@inheritdoc}
|
2017 |
+
*/
|
2018 |
+
public function getId()
|
2019 |
+
{
|
2020 |
+
return 'SETBIT';
|
2021 |
+
}
|
2022 |
+
}
|
2023 |
+
|
2024 |
+
/**
|
2025 |
+
* @link http://redis.io/commands/psetex
|
2026 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2027 |
+
*/
|
2028 |
+
class StringPreciseSetExpire extends StringSetExpire
|
2029 |
+
{
|
2030 |
+
/**
|
2031 |
+
* {@inheritdoc}
|
2032 |
+
*/
|
2033 |
+
public function getId()
|
2034 |
+
{
|
2035 |
+
return 'PSETEX';
|
2036 |
+
}
|
2037 |
+
}
|
2038 |
+
|
2039 |
+
/**
|
2040 |
+
* @link http://redis.io/commands/incrbyfloat
|
2041 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2042 |
+
*/
|
2043 |
+
class StringIncrementByFloat extends Command
|
2044 |
+
{
|
2045 |
+
/**
|
2046 |
+
* {@inheritdoc}
|
2047 |
+
*/
|
2048 |
+
public function getId()
|
2049 |
+
{
|
2050 |
+
return 'INCRBYFLOAT';
|
2051 |
+
}
|
2052 |
+
}
|
2053 |
+
|
2054 |
+
/**
|
2055 |
+
* @link http://redis.io/commands/incrby
|
2056 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2057 |
+
*/
|
2058 |
+
class StringIncrementBy extends Command
|
2059 |
+
{
|
2060 |
+
/**
|
2061 |
+
* {@inheritdoc}
|
2062 |
+
*/
|
2063 |
+
public function getId()
|
2064 |
+
{
|
2065 |
+
return 'INCRBY';
|
2066 |
+
}
|
2067 |
+
}
|
2068 |
+
|
2069 |
+
/**
|
2070 |
+
* @link http://redis.io/commands/save
|
2071 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2072 |
+
*/
|
2073 |
+
class ServerSave extends Command
|
2074 |
+
{
|
2075 |
+
/**
|
2076 |
+
* {@inheritdoc}
|
2077 |
+
*/
|
2078 |
+
public function getId()
|
2079 |
+
{
|
2080 |
+
return 'SAVE';
|
2081 |
+
}
|
2082 |
+
}
|
2083 |
+
|
2084 |
+
/**
|
2085 |
+
* @link http://redis.io/commands/decr
|
2086 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2087 |
+
*/
|
2088 |
+
class StringDecrement extends Command
|
2089 |
+
{
|
2090 |
+
/**
|
2091 |
+
* {@inheritdoc}
|
2092 |
+
*/
|
2093 |
+
public function getId()
|
2094 |
+
{
|
2095 |
+
return 'DECR';
|
2096 |
+
}
|
2097 |
+
}
|
2098 |
+
|
2099 |
+
/**
|
2100 |
+
* @link http://redis.io/commands/flushall
|
2101 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2102 |
+
*/
|
2103 |
+
class ServerFlushAll extends Command
|
2104 |
+
{
|
2105 |
+
/**
|
2106 |
+
* {@inheritdoc}
|
2107 |
+
*/
|
2108 |
+
public function getId()
|
2109 |
+
{
|
2110 |
+
return 'FLUSHALL';
|
2111 |
+
}
|
2112 |
+
}
|
2113 |
+
|
2114 |
+
/**
|
2115 |
+
* @link http://redis.io/commands/del
|
2116 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2117 |
+
*/
|
2118 |
+
class KeyDelete extends Command
|
2119 |
+
{
|
2120 |
+
/**
|
2121 |
+
* {@inheritdoc}
|
2122 |
+
*/
|
2123 |
+
public function getId()
|
2124 |
+
{
|
2125 |
+
return 'DEL';
|
2126 |
+
}
|
2127 |
+
|
2128 |
+
/**
|
2129 |
+
* {@inheritdoc}
|
2130 |
+
*/
|
2131 |
+
protected function filterArguments(array $arguments)
|
2132 |
+
{
|
2133 |
+
return self::normalizeArguments($arguments);
|
2134 |
+
}
|
2135 |
+
}
|
2136 |
+
|
2137 |
+
/**
|
2138 |
+
* @link http://redis.io/commands/dump
|
2139 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2140 |
+
*/
|
2141 |
+
class KeyDump extends Command
|
2142 |
+
{
|
2143 |
+
/**
|
2144 |
+
* {@inheritdoc}
|
2145 |
+
*/
|
2146 |
+
public function getId()
|
2147 |
+
{
|
2148 |
+
return 'DUMP';
|
2149 |
+
}
|
2150 |
+
}
|
2151 |
+
|
2152 |
+
/**
|
2153 |
+
* @link http://redis.io/commands/exists
|
2154 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2155 |
+
*/
|
2156 |
+
class KeyExists extends Command
|
2157 |
+
{
|
2158 |
+
/**
|
2159 |
+
* {@inheritdoc}
|
2160 |
+
*/
|
2161 |
+
public function getId()
|
2162 |
+
{
|
2163 |
+
return 'EXISTS';
|
2164 |
+
}
|
2165 |
+
|
2166 |
+
/**
|
2167 |
+
* {@inheritdoc}
|
2168 |
+
*/
|
2169 |
+
public function parseResponse($data)
|
2170 |
+
{
|
2171 |
+
return (bool) $data;
|
2172 |
+
}
|
2173 |
+
}
|
2174 |
+
|
2175 |
+
/**
|
2176 |
+
* @link http://redis.io/commands/pfmerge
|
2177 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2178 |
+
*/
|
2179 |
+
class HyperLogLogMerge extends Command
|
2180 |
+
{
|
2181 |
+
/**
|
2182 |
+
* {@inheritdoc}
|
2183 |
+
*/
|
2184 |
+
public function getId()
|
2185 |
+
{
|
2186 |
+
return 'PFMERGE';
|
2187 |
+
}
|
2188 |
+
|
2189 |
+
/**
|
2190 |
+
* {@inheritdoc}
|
2191 |
+
*/
|
2192 |
+
protected function filterArguments(array $arguments)
|
2193 |
+
{
|
2194 |
+
return self::normalizeArguments($arguments);
|
2195 |
+
}
|
2196 |
+
}
|
2197 |
+
|
2198 |
+
/**
|
2199 |
+
* @link http://redis.io/commands/pfcount
|
2200 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2201 |
+
*/
|
2202 |
+
class HyperLogLogCount extends Command
|
2203 |
+
{
|
2204 |
+
/**
|
2205 |
+
* {@inheritdoc}
|
2206 |
+
*/
|
2207 |
+
public function getId()
|
2208 |
+
{
|
2209 |
+
return 'PFCOUNT';
|
2210 |
+
}
|
2211 |
+
|
2212 |
+
/**
|
2213 |
+
* {@inheritdoc}
|
2214 |
+
*/
|
2215 |
+
protected function filterArguments(array $arguments)
|
2216 |
+
{
|
2217 |
+
return self::normalizeArguments($arguments);
|
2218 |
+
}
|
2219 |
+
}
|
2220 |
+
|
2221 |
+
/**
|
2222 |
+
* @link http://redis.io/commands/hvals
|
2223 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2224 |
+
*/
|
2225 |
+
class HashValues extends Command
|
2226 |
+
{
|
2227 |
+
/**
|
2228 |
+
* {@inheritdoc}
|
2229 |
+
*/
|
2230 |
+
public function getId()
|
2231 |
+
{
|
2232 |
+
return 'HVALS';
|
2233 |
+
}
|
2234 |
+
}
|
2235 |
+
|
2236 |
+
/**
|
2237 |
+
* @link http://redis.io/commands/pfadd
|
2238 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2239 |
+
*/
|
2240 |
+
class HyperLogLogAdd extends Command
|
2241 |
+
{
|
2242 |
+
/**
|
2243 |
+
* {@inheritdoc}
|
2244 |
+
*/
|
2245 |
+
public function getId()
|
2246 |
+
{
|
2247 |
+
return 'PFADD';
|
2248 |
+
}
|
2249 |
+
|
2250 |
+
/**
|
2251 |
+
* {@inheritdoc}
|
2252 |
+
*/
|
2253 |
+
protected function filterArguments(array $arguments)
|
2254 |
+
{
|
2255 |
+
return self::normalizeVariadic($arguments);
|
2256 |
+
}
|
2257 |
+
|
2258 |
+
/**
|
2259 |
+
* {@inheritdoc}
|
2260 |
+
*/
|
2261 |
+
public function parseResponse($data)
|
2262 |
+
{
|
2263 |
+
return (bool) $data;
|
2264 |
+
}
|
2265 |
+
}
|
2266 |
+
|
2267 |
+
/**
|
2268 |
+
* @link http://redis.io/commands/keys
|
2269 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2270 |
+
*/
|
2271 |
+
class KeyKeys extends Command
|
2272 |
+
{
|
2273 |
+
/**
|
2274 |
+
* {@inheritdoc}
|
2275 |
+
*/
|
2276 |
+
public function getId()
|
2277 |
+
{
|
2278 |
+
return 'KEYS';
|
2279 |
+
}
|
2280 |
+
}
|
2281 |
+
|
2282 |
+
/**
|
2283 |
+
* @link http://redis.io/commands/move
|
2284 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2285 |
+
*/
|
2286 |
+
class KeyMove extends Command
|
2287 |
+
{
|
2288 |
+
/**
|
2289 |
+
* {@inheritdoc}
|
2290 |
+
*/
|
2291 |
+
public function getId()
|
2292 |
+
{
|
2293 |
+
return 'MOVE';
|
2294 |
+
}
|
2295 |
+
|
2296 |
+
/**
|
2297 |
+
* {@inheritdoc}
|
2298 |
+
*/
|
2299 |
+
public function parseResponse($data)
|
2300 |
+
{
|
2301 |
+
return (bool) $data;
|
2302 |
+
}
|
2303 |
+
}
|
2304 |
+
|
2305 |
+
/**
|
2306 |
+
* @link http://redis.io/commands/randomkey
|
2307 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2308 |
+
*/
|
2309 |
+
class KeyRandom extends Command
|
2310 |
+
{
|
2311 |
+
/**
|
2312 |
+
* {@inheritdoc}
|
2313 |
+
*/
|
2314 |
+
public function getId()
|
2315 |
+
{
|
2316 |
+
return 'RANDOMKEY';
|
2317 |
+
}
|
2318 |
+
|
2319 |
+
/**
|
2320 |
+
* {@inheritdoc}
|
2321 |
+
*/
|
2322 |
+
public function parseResponse($data)
|
2323 |
+
{
|
2324 |
+
return $data !== '' ? $data : null;
|
2325 |
+
}
|
2326 |
+
}
|
2327 |
+
|
2328 |
+
/**
|
2329 |
+
* @link http://redis.io/commands/renamenx
|
2330 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2331 |
+
*/
|
2332 |
+
class KeyRenamePreserve extends KeyRename
|
2333 |
+
{
|
2334 |
+
/**
|
2335 |
+
* {@inheritdoc}
|
2336 |
+
*/
|
2337 |
+
public function getId()
|
2338 |
+
{
|
2339 |
+
return 'RENAMENX';
|
2340 |
+
}
|
2341 |
+
|
2342 |
+
/**
|
2343 |
+
* {@inheritdoc}
|
2344 |
+
*/
|
2345 |
+
public function parseResponse($data)
|
2346 |
+
{
|
2347 |
+
return (bool) $data;
|
2348 |
+
}
|
2349 |
+
}
|
2350 |
+
|
2351 |
+
/**
|
2352 |
+
* @link http://redis.io/commands/restore
|
2353 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2354 |
+
*/
|
2355 |
+
class KeyRestore extends Command
|
2356 |
+
{
|
2357 |
+
/**
|
2358 |
+
* {@inheritdoc}
|
2359 |
+
*/
|
2360 |
+
public function getId()
|
2361 |
+
{
|
2362 |
+
return 'RESTORE';
|
2363 |
+
}
|
2364 |
+
}
|
2365 |
+
|
2366 |
+
/**
|
2367 |
+
* @link http://redis.io/commands/pttl
|
2368 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2369 |
+
*/
|
2370 |
+
class KeyPreciseTimeToLive extends KeyTimeToLive
|
2371 |
+
{
|
2372 |
+
/**
|
2373 |
+
* {@inheritdoc}
|
2374 |
+
*/
|
2375 |
+
public function getId()
|
2376 |
+
{
|
2377 |
+
return 'PTTL';
|
2378 |
+
}
|
2379 |
+
}
|
2380 |
+
|
2381 |
+
/**
|
2382 |
+
* @link http://redis.io/commands/pexpireat
|
2383 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2384 |
+
*/
|
2385 |
+
class KeyPreciseExpireAt extends KeyExpireAt
|
2386 |
+
{
|
2387 |
+
/**
|
2388 |
+
* {@inheritdoc}
|
2389 |
+
*/
|
2390 |
+
public function getId()
|
2391 |
+
{
|
2392 |
+
return 'PEXPIREAT';
|
2393 |
+
}
|
2394 |
+
}
|
2395 |
+
|
2396 |
+
/**
|
2397 |
+
* @link http://redis.io/commands/persist
|
2398 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2399 |
+
*/
|
2400 |
+
class KeyPersist extends Command
|
2401 |
+
{
|
2402 |
+
/**
|
2403 |
+
* {@inheritdoc}
|
2404 |
+
*/
|
2405 |
+
public function getId()
|
2406 |
+
{
|
2407 |
+
return 'PERSIST';
|
2408 |
+
}
|
2409 |
+
|
2410 |
+
/**
|
2411 |
+
* {@inheritdoc}
|
2412 |
+
*/
|
2413 |
+
public function parseResponse($data)
|
2414 |
+
{
|
2415 |
+
return (bool) $data;
|
2416 |
+
}
|
2417 |
+
}
|
2418 |
+
|
2419 |
+
/**
|
2420 |
+
* @link http://redis.io/commands/pexpire
|
2421 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2422 |
+
*/
|
2423 |
+
class KeyPreciseExpire extends KeyExpire
|
2424 |
+
{
|
2425 |
+
/**
|
2426 |
+
* {@inheritdoc}
|
2427 |
+
*/
|
2428 |
+
public function getId()
|
2429 |
+
{
|
2430 |
+
return 'PEXPIRE';
|
2431 |
+
}
|
2432 |
+
}
|
2433 |
+
|
2434 |
+
/**
|
2435 |
+
* @link http://redis.io/commands/hsetnx
|
2436 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2437 |
+
*/
|
2438 |
+
class HashSetPreserve extends Command
|
2439 |
+
{
|
2440 |
+
/**
|
2441 |
+
* {@inheritdoc}
|
2442 |
+
*/
|
2443 |
+
public function getId()
|
2444 |
+
{
|
2445 |
+
return 'HSETNX';
|
2446 |
+
}
|
2447 |
+
|
2448 |
+
/**
|
2449 |
+
* {@inheritdoc}
|
2450 |
+
*/
|
2451 |
+
public function parseResponse($data)
|
2452 |
+
{
|
2453 |
+
return (bool) $data;
|
2454 |
+
}
|
2455 |
+
}
|
2456 |
+
|
2457 |
+
/**
|
2458 |
+
* @link http://redis.io/commands/hmset
|
2459 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2460 |
+
*/
|
2461 |
+
class HashSetMultiple extends Command
|
2462 |
+
{
|
2463 |
+
/**
|
2464 |
+
* {@inheritdoc}
|
2465 |
+
*/
|
2466 |
+
public function getId()
|
2467 |
+
{
|
2468 |
+
return 'HMSET';
|
2469 |
+
}
|
2470 |
+
|
2471 |
+
/**
|
2472 |
+
* {@inheritdoc}
|
2473 |
+
*/
|
2474 |
+
protected function filterArguments(array $arguments)
|
2475 |
+
{
|
2476 |
+
if (count($arguments) === 2 && is_array($arguments[1])) {
|
2477 |
+
$flattenedKVs = array($arguments[0]);
|
2478 |
+
$args = $arguments[1];
|
2479 |
+
|
2480 |
+
foreach ($args as $k => $v) {
|
2481 |
+
$flattenedKVs[] = $k;
|
2482 |
+
$flattenedKVs[] = $v;
|
2483 |
+
}
|
2484 |
+
|
2485 |
+
return $flattenedKVs;
|
2486 |
+
}
|
2487 |
+
|
2488 |
+
return $arguments;
|
2489 |
+
}
|
2490 |
+
}
|
2491 |
+
|
2492 |
+
/**
|
2493 |
+
* @link http://redis.io/commands/select
|
2494 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2495 |
+
*/
|
2496 |
+
class ConnectionSelect extends Command
|
2497 |
+
{
|
2498 |
+
/**
|
2499 |
+
* {@inheritdoc}
|
2500 |
+
*/
|
2501 |
+
public function getId()
|
2502 |
+
{
|
2503 |
+
return 'SELECT';
|
2504 |
+
}
|
2505 |
+
}
|
2506 |
+
|
2507 |
+
/**
|
2508 |
+
* @link http://redis.io/commands/hdel
|
2509 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2510 |
+
*/
|
2511 |
+
class HashDelete extends Command
|
2512 |
+
{
|
2513 |
+
/**
|
2514 |
+
* {@inheritdoc}
|
2515 |
+
*/
|
2516 |
+
public function getId()
|
2517 |
+
{
|
2518 |
+
return 'HDEL';
|
2519 |
+
}
|
2520 |
+
|
2521 |
+
/**
|
2522 |
+
* {@inheritdoc}
|
2523 |
+
*/
|
2524 |
+
protected function filterArguments(array $arguments)
|
2525 |
+
{
|
2526 |
+
return self::normalizeVariadic($arguments);
|
2527 |
+
}
|
2528 |
+
}
|
2529 |
+
|
2530 |
+
/**
|
2531 |
+
* @link http://redis.io/commands/hexists
|
2532 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2533 |
+
*/
|
2534 |
+
class HashExists extends Command
|
2535 |
+
{
|
2536 |
+
/**
|
2537 |
+
* {@inheritdoc}
|
2538 |
+
*/
|
2539 |
+
public function getId()
|
2540 |
+
{
|
2541 |
+
return 'HEXISTS';
|
2542 |
+
}
|
2543 |
+
|
2544 |
+
/**
|
2545 |
+
* {@inheritdoc}
|
2546 |
+
*/
|
2547 |
+
public function parseResponse($data)
|
2548 |
+
{
|
2549 |
+
return (bool) $data;
|
2550 |
+
}
|
2551 |
+
}
|
2552 |
+
|
2553 |
+
/**
|
2554 |
+
* @link http://redis.io/commands/quit
|
2555 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2556 |
+
*/
|
2557 |
+
class ConnectionQuit extends Command
|
2558 |
+
{
|
2559 |
+
/**
|
2560 |
+
* {@inheritdoc}
|
2561 |
+
*/
|
2562 |
+
public function getId()
|
2563 |
+
{
|
2564 |
+
return 'QUIT';
|
2565 |
+
}
|
2566 |
+
}
|
2567 |
+
|
2568 |
+
/**
|
2569 |
+
* @link http://redis.io/commands/ping
|
2570 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2571 |
+
*/
|
2572 |
+
class ConnectionPing extends Command
|
2573 |
+
{
|
2574 |
+
/**
|
2575 |
+
* {@inheritdoc}
|
2576 |
+
*/
|
2577 |
+
public function getId()
|
2578 |
+
{
|
2579 |
+
return 'PING';
|
2580 |
+
}
|
2581 |
+
}
|
2582 |
+
|
2583 |
+
/**
|
2584 |
+
* @link http://redis.io/commands/auth
|
2585 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2586 |
+
*/
|
2587 |
+
class ConnectionAuth extends Command
|
2588 |
+
{
|
2589 |
+
/**
|
2590 |
+
* {@inheritdoc}
|
2591 |
+
*/
|
2592 |
+
public function getId()
|
2593 |
+
{
|
2594 |
+
return 'AUTH';
|
2595 |
+
}
|
2596 |
+
}
|
2597 |
+
|
2598 |
+
/**
|
2599 |
+
* @link http://redis.io/commands/echo
|
2600 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2601 |
+
*/
|
2602 |
+
class ConnectionEcho extends Command
|
2603 |
+
{
|
2604 |
+
/**
|
2605 |
+
* {@inheritdoc}
|
2606 |
+
*/
|
2607 |
+
public function getId()
|
2608 |
+
{
|
2609 |
+
return 'ECHO';
|
2610 |
+
}
|
2611 |
+
}
|
2612 |
+
|
2613 |
+
/**
|
2614 |
+
* @link http://redis.io/commands/hget
|
2615 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2616 |
+
*/
|
2617 |
+
class HashGet extends Command
|
2618 |
+
{
|
2619 |
+
/**
|
2620 |
+
* {@inheritdoc}
|
2621 |
+
*/
|
2622 |
+
public function getId()
|
2623 |
+
{
|
2624 |
+
return 'HGET';
|
2625 |
+
}
|
2626 |
+
}
|
2627 |
+
|
2628 |
+
/**
|
2629 |
+
* @link http://redis.io/commands/hgetall
|
2630 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2631 |
+
*/
|
2632 |
+
class HashGetAll extends Command
|
2633 |
+
{
|
2634 |
+
/**
|
2635 |
+
* {@inheritdoc}
|
2636 |
+
*/
|
2637 |
+
public function getId()
|
2638 |
+
{
|
2639 |
+
return 'HGETALL';
|
2640 |
+
}
|
2641 |
+
|
2642 |
+
/**
|
2643 |
+
* {@inheritdoc}
|
2644 |
+
*/
|
2645 |
+
public function parseResponse($data)
|
2646 |
+
{
|
2647 |
+
$result = array();
|
2648 |
+
|
2649 |
+
for ($i = 0; $i < count($data); $i++) {
|
2650 |
+
$result[$data[$i]] = $data[++$i];
|
2651 |
+
}
|
2652 |
+
|
2653 |
+
return $result;
|
2654 |
+
}
|
2655 |
+
}
|
2656 |
+
|
2657 |
+
/**
|
2658 |
+
* @link http://redis.io/commands/hlen
|
2659 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2660 |
+
*/
|
2661 |
+
class HashLength extends Command
|
2662 |
+
{
|
2663 |
+
/**
|
2664 |
+
* {@inheritdoc}
|
2665 |
+
*/
|
2666 |
+
public function getId()
|
2667 |
+
{
|
2668 |
+
return 'HLEN';
|
2669 |
+
}
|
2670 |
+
}
|
2671 |
+
|
2672 |
+
/**
|
2673 |
+
* @link http://redis.io/commands/hscan
|
2674 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2675 |
+
*/
|
2676 |
+
class HashScan extends Command
|
2677 |
+
{
|
2678 |
+
/**
|
2679 |
+
* {@inheritdoc}
|
2680 |
+
*/
|
2681 |
+
public function getId()
|
2682 |
+
{
|
2683 |
+
return 'HSCAN';
|
2684 |
+
}
|
2685 |
+
|
2686 |
+
/**
|
2687 |
+
* {@inheritdoc}
|
2688 |
+
*/
|
2689 |
+
protected function filterArguments(array $arguments)
|
2690 |
+
{
|
2691 |
+
if (count($arguments) === 3 && is_array($arguments[2])) {
|
2692 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
2693 |
+
$arguments = array_merge($arguments, $options);
|
2694 |
+
}
|
2695 |
+
|
2696 |
+
return $arguments;
|
2697 |
+
}
|
2698 |
+
|
2699 |
+
/**
|
2700 |
+
* Returns a list of options and modifiers compatible with Redis.
|
2701 |
+
*
|
2702 |
+
* @param array $options List of options.
|
2703 |
+
*
|
2704 |
+
* @return array
|
2705 |
+
*/
|
2706 |
+
protected function prepareOptions($options)
|
2707 |
+
{
|
2708 |
+
$options = array_change_key_case($options, CASE_UPPER);
|
2709 |
+
$normalized = array();
|
2710 |
+
|
2711 |
+
if (!empty($options['MATCH'])) {
|
2712 |
+
$normalized[] = 'MATCH';
|
2713 |
+
$normalized[] = $options['MATCH'];
|
2714 |
+
}
|
2715 |
+
|
2716 |
+
if (!empty($options['COUNT'])) {
|
2717 |
+
$normalized[] = 'COUNT';
|
2718 |
+
$normalized[] = $options['COUNT'];
|
2719 |
+
}
|
2720 |
+
|
2721 |
+
return $normalized;
|
2722 |
+
}
|
2723 |
+
|
2724 |
+
/**
|
2725 |
+
* {@inheritdoc}
|
2726 |
+
*/
|
2727 |
+
public function parseResponse($data)
|
2728 |
+
{
|
2729 |
+
if (is_array($data)) {
|
2730 |
+
$fields = $data[1];
|
2731 |
+
$result = array();
|
2732 |
+
|
2733 |
+
for ($i = 0; $i < count($fields); $i++) {
|
2734 |
+
$result[$fields[$i]] = $fields[++$i];
|
2735 |
+
}
|
2736 |
+
|
2737 |
+
$data[1] = $result;
|
2738 |
+
}
|
2739 |
+
|
2740 |
+
return $data;
|
2741 |
+
}
|
2742 |
+
}
|
2743 |
+
|
2744 |
+
/**
|
2745 |
+
* @link http://redis.io/commands/hset
|
2746 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2747 |
+
*/
|
2748 |
+
class HashSet extends Command
|
2749 |
+
{
|
2750 |
+
/**
|
2751 |
+
* {@inheritdoc}
|
2752 |
+
*/
|
2753 |
+
public function getId()
|
2754 |
+
{
|
2755 |
+
return 'HSET';
|
2756 |
+
}
|
2757 |
+
|
2758 |
+
/**
|
2759 |
+
* {@inheritdoc}
|
2760 |
+
*/
|
2761 |
+
public function parseResponse($data)
|
2762 |
+
{
|
2763 |
+
return (bool) $data;
|
2764 |
+
}
|
2765 |
+
}
|
2766 |
+
|
2767 |
+
/**
|
2768 |
+
* @link http://redis.io/commands/hkeys
|
2769 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2770 |
+
*/
|
2771 |
+
class HashKeys extends Command
|
2772 |
+
{
|
2773 |
+
/**
|
2774 |
+
* {@inheritdoc}
|
2775 |
+
*/
|
2776 |
+
public function getId()
|
2777 |
+
{
|
2778 |
+
return 'HKEYS';
|
2779 |
+
}
|
2780 |
+
}
|
2781 |
+
|
2782 |
+
/**
|
2783 |
+
* @link http://redis.io/commands/hincrbyfloat
|
2784 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2785 |
+
*/
|
2786 |
+
class HashIncrementByFloat extends Command
|
2787 |
+
{
|
2788 |
+
/**
|
2789 |
+
* {@inheritdoc}
|
2790 |
+
*/
|
2791 |
+
public function getId()
|
2792 |
+
{
|
2793 |
+
return 'HINCRBYFLOAT';
|
2794 |
+
}
|
2795 |
+
}
|
2796 |
+
|
2797 |
+
/**
|
2798 |
+
* @link http://redis.io/commands/hmget
|
2799 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2800 |
+
*/
|
2801 |
+
class HashGetMultiple extends Command
|
2802 |
+
{
|
2803 |
+
/**
|
2804 |
+
* {@inheritdoc}
|
2805 |
+
*/
|
2806 |
+
public function getId()
|
2807 |
+
{
|
2808 |
+
return 'HMGET';
|
2809 |
+
}
|
2810 |
+
|
2811 |
+
/**
|
2812 |
+
* {@inheritdoc}
|
2813 |
+
*/
|
2814 |
+
protected function filterArguments(array $arguments)
|
2815 |
+
{
|
2816 |
+
return self::normalizeVariadic($arguments);
|
2817 |
+
}
|
2818 |
+
}
|
2819 |
+
|
2820 |
+
/**
|
2821 |
+
* @link http://redis.io/commands/hincrby
|
2822 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2823 |
+
*/
|
2824 |
+
class HashIncrementBy extends Command
|
2825 |
+
{
|
2826 |
+
/**
|
2827 |
+
* {@inheritdoc}
|
2828 |
+
*/
|
2829 |
+
public function getId()
|
2830 |
+
{
|
2831 |
+
return 'HINCRBY';
|
2832 |
+
}
|
2833 |
+
}
|
2834 |
+
|
2835 |
+
/**
|
2836 |
+
* @link http://redis.io/commands/scan
|
2837 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2838 |
+
*/
|
2839 |
+
class KeyScan extends Command
|
2840 |
+
{
|
2841 |
+
/**
|
2842 |
+
* {@inheritdoc}
|
2843 |
+
*/
|
2844 |
+
public function getId()
|
2845 |
+
{
|
2846 |
+
return 'SCAN';
|
2847 |
+
}
|
2848 |
+
|
2849 |
+
/**
|
2850 |
+
* {@inheritdoc}
|
2851 |
+
*/
|
2852 |
+
protected function filterArguments(array $arguments)
|
2853 |
+
{
|
2854 |
+
if (count($arguments) === 2 && is_array($arguments[1])) {
|
2855 |
+
$options = $this->prepareOptions(array_pop($arguments));
|
2856 |
+
$arguments = array_merge($arguments, $options);
|
2857 |
+
}
|
2858 |
+
|
2859 |
+
return $arguments;
|
2860 |
+
}
|
2861 |
+
|
2862 |
+
/**
|
2863 |
+
* Returns a list of options and modifiers compatible with Redis.
|
2864 |
+
*
|
2865 |
+
* @param array $options List of options.
|
2866 |
+
*
|
2867 |
+
* @return array
|
2868 |
+
*/
|
2869 |
+
protected function prepareOptions($options)
|
2870 |
+
{
|
2871 |
+
$options = array_change_key_case($options, CASE_UPPER);
|
2872 |
+
$normalized = array();
|
2873 |
+
|
2874 |
+
if (!empty($options['MATCH'])) {
|
2875 |
+
$normalized[] = 'MATCH';
|
2876 |
+
$normalized[] = $options['MATCH'];
|
2877 |
+
}
|
2878 |
+
|
2879 |
+
if (!empty($options['COUNT'])) {
|
2880 |
+
$normalized[] = 'COUNT';
|
2881 |
+
$normalized[] = $options['COUNT'];
|
2882 |
+
}
|
2883 |
+
|
2884 |
+
return $normalized;
|
2885 |
+
}
|
2886 |
+
}
|
2887 |
+
|
2888 |
+
/**
|
2889 |
+
* @link http://redis.io/commands/sort
|
2890 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2891 |
+
*/
|
2892 |
+
class KeySort extends Command
|
2893 |
+
{
|
2894 |
+
/**
|
2895 |
+
* {@inheritdoc}
|
2896 |
+
*/
|
2897 |
+
public function getId()
|
2898 |
+
{
|
2899 |
+
return 'SORT';
|
2900 |
+
}
|
2901 |
+
|
2902 |
+
/**
|
2903 |
+
* {@inheritdoc}
|
2904 |
+
*/
|
2905 |
+
protected function filterArguments(array $arguments)
|
2906 |
+
{
|
2907 |
+
if (count($arguments) === 1) {
|
2908 |
+
return $arguments;
|
2909 |
+
}
|
2910 |
+
|
2911 |
+
$query = array($arguments[0]);
|
2912 |
+
$sortParams = array_change_key_case($arguments[1], CASE_UPPER);
|
2913 |
+
|
2914 |
+
if (isset($sortParams['BY'])) {
|
2915 |
+
$query[] = 'BY';
|
2916 |
+
$query[] = $sortParams['BY'];
|
2917 |
+
}
|
2918 |
+
|
2919 |
+
if (isset($sortParams['GET'])) {
|
2920 |
+
$getargs = $sortParams['GET'];
|
2921 |
+
|
2922 |
+
if (is_array($getargs)) {
|
2923 |
+
foreach ($getargs as $getarg) {
|
2924 |
+
$query[] = 'GET';
|
2925 |
+
$query[] = $getarg;
|
2926 |
+
}
|
2927 |
+
} else {
|
2928 |
+
$query[] = 'GET';
|
2929 |
+
$query[] = $getargs;
|
2930 |
+
}
|
2931 |
+
}
|
2932 |
+
|
2933 |
+
if (isset($sortParams['LIMIT']) &&
|
2934 |
+
is_array($sortParams['LIMIT']) &&
|
2935 |
+
count($sortParams['LIMIT']) == 2) {
|
2936 |
+
|
2937 |
+
$query[] = 'LIMIT';
|
2938 |
+
$query[] = $sortParams['LIMIT'][0];
|
2939 |
+
$query[] = $sortParams['LIMIT'][1];
|
2940 |
+
}
|
2941 |
+
|
2942 |
+
if (isset($sortParams['SORT'])) {
|
2943 |
+
$query[] = strtoupper($sortParams['SORT']);
|
2944 |
+
}
|
2945 |
+
|
2946 |
+
if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
|
2947 |
+
$query[] = 'ALPHA';
|
2948 |
+
}
|
2949 |
+
|
2950 |
+
if (isset($sortParams['STORE'])) {
|
2951 |
+
$query[] = 'STORE';
|
2952 |
+
$query[] = $sortParams['STORE'];
|
2953 |
+
}
|
2954 |
+
|
2955 |
+
return $query;
|
2956 |
+
}
|
2957 |
+
}
|
2958 |
+
|
2959 |
+
/**
|
2960 |
+
* Class for generic "anonymous" Redis commands.
|
2961 |
+
*
|
2962 |
+
* This command class does not filter input arguments or parse responses, but
|
2963 |
+
* can be used to leverage the standard Predis API to execute any command simply
|
2964 |
+
* by providing the needed arguments following the command signature as defined
|
2965 |
+
* by Redis in its documentation.
|
2966 |
+
*
|
2967 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
2968 |
+
*/
|
2969 |
+
class RawCommand implements CommandInterface
|
2970 |
+
{
|
2971 |
+
private $slot;
|
2972 |
+
private $commandID;
|
2973 |
+
private $arguments;
|
2974 |
+
|
2975 |
+
/**
|
2976 |
+
* @param array $arguments Command ID and its arguments.
|
2977 |
+
*
|
2978 |
+
* @throws \InvalidArgumentException
|
2979 |
+
*/
|
2980 |
+
public function __construct(array $arguments)
|
2981 |
+
{
|
2982 |
+
if (!$arguments) {
|
2983 |
+
throw new InvalidArgumentException(
|
2984 |
+
'The arguments array must contain at least the command ID.'
|
2985 |
+
);
|
2986 |
+
}
|
2987 |
+
|
2988 |
+
$this->commandID = strtoupper(array_shift($arguments));
|
2989 |
+
$this->arguments = $arguments;
|
2990 |
+
}
|
2991 |
+
|
2992 |
+
/**
|
2993 |
+
* Creates a new raw command using a variadic method.
|
2994 |
+
*
|
2995 |
+
* @param string $commandID Redis command ID.
|
2996 |
+
* @param string ... Arguments list for the command.
|
2997 |
+
*
|
2998 |
+
* @return CommandInterface
|
2999 |
+
*/
|
3000 |
+
public static function create($commandID /* [ $arg, ... */)
|
3001 |
+
{
|
3002 |
+
$arguments = func_get_args();
|
3003 |
+
$command = new self($arguments);
|
3004 |
+
|
3005 |
+
return $command;
|
3006 |
+
}
|
3007 |
+
|
3008 |
+
/**
|
3009 |
+
* {@inheritdoc}
|
3010 |
+
*/
|
3011 |
+
public function getId()
|
3012 |
+
{
|
3013 |
+
return $this->commandID;
|
3014 |
+
}
|
3015 |
+
|
3016 |
+
/**
|
3017 |
+
* {@inheritdoc}
|
3018 |
+
*/
|
3019 |
+
public function setArguments(array $arguments)
|
3020 |
+
{
|
3021 |
+
$this->arguments = $arguments;
|
3022 |
+
unset($this->slot);
|
3023 |
+
}
|
3024 |
+
|
3025 |
+
/**
|
3026 |
+
* {@inheritdoc}
|
3027 |
+
*/
|
3028 |
+
public function setRawArguments(array $arguments)
|
3029 |
+
{
|
3030 |
+
$this->setArguments($arguments);
|
3031 |
+
}
|
3032 |
+
|
3033 |
+
/**
|
3034 |
+
* {@inheritdoc}
|
3035 |
+
*/
|
3036 |
+
public function getArguments()
|
3037 |
+
{
|
3038 |
+
return $this->arguments;
|
3039 |
+
}
|
3040 |
+
|
3041 |
+
/**
|
3042 |
+
* {@inheritdoc}
|
3043 |
+
*/
|
3044 |
+
public function getArgument($index)
|
3045 |
+
{
|
3046 |
+
if (isset($this->arguments[$index])) {
|
3047 |
+
return $this->arguments[$index];
|
3048 |
+
}
|
3049 |
+
}
|
3050 |
+
|
3051 |
+
/**
|
3052 |
+
* {@inheritdoc}
|
3053 |
+
*/
|
3054 |
+
public function setSlot($slot)
|
3055 |
+
{
|
3056 |
+
$this->slot = $slot;
|
3057 |
+
}
|
3058 |
+
|
3059 |
+
/**
|
3060 |
+
* {@inheritdoc}
|
3061 |
+
*/
|
3062 |
+
public function getSlot()
|
3063 |
+
{
|
3064 |
+
if (isset($this->slot)) {
|
3065 |
+
return $this->slot;
|
3066 |
+
}
|
3067 |
+
}
|
3068 |
+
|
3069 |
+
/**
|
3070 |
+
* {@inheritdoc}
|
3071 |
+
*/
|
3072 |
+
public function parseResponse($data)
|
3073 |
+
{
|
3074 |
+
return $data;
|
3075 |
+
}
|
3076 |
+
}
|
3077 |
+
|
3078 |
+
/**
|
3079 |
+
* Base class used to implement an higher level abstraction for commands based
|
3080 |
+
* on Lua scripting with EVAL and EVALSHA.
|
3081 |
+
*
|
3082 |
+
* @link http://redis.io/commands/eval
|
3083 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3084 |
+
*/
|
3085 |
+
abstract class ScriptCommand extends ServerEvalSHA
|
3086 |
+
{
|
3087 |
+
/**
|
3088 |
+
* Gets the body of a Lua script.
|
3089 |
+
*
|
3090 |
+
* @return string
|
3091 |
+
*/
|
3092 |
+
abstract public function getScript();
|
3093 |
+
|
3094 |
+
/**
|
3095 |
+
* Specifies the number of arguments that should be considered as keys.
|
3096 |
+
*
|
3097 |
+
* The default behaviour for the base class is to return 0 to indicate that
|
3098 |
+
* all the elements of the arguments array should be considered as keys, but
|
3099 |
+
* subclasses can enforce a static number of keys.
|
3100 |
+
*
|
3101 |
+
* @return int
|
3102 |
+
*/
|
3103 |
+
protected function getKeysCount()
|
3104 |
+
{
|
3105 |
+
return 0;
|
3106 |
+
}
|
3107 |
+
|
3108 |
+
/**
|
3109 |
+
* Returns the elements from the arguments that are identified as keys.
|
3110 |
+
*
|
3111 |
+
* @return array
|
3112 |
+
*/
|
3113 |
+
public function getKeys()
|
3114 |
+
{
|
3115 |
+
return array_slice($this->getArguments(), 2, $this->getKeysCount());
|
3116 |
+
}
|
3117 |
+
|
3118 |
+
/**
|
3119 |
+
* {@inheritdoc}
|
3120 |
+
*/
|
3121 |
+
protected function filterArguments(array $arguments)
|
3122 |
+
{
|
3123 |
+
if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
|
3124 |
+
$numkeys = count($arguments) + $numkeys;
|
3125 |
+
}
|
3126 |
+
|
3127 |
+
return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
|
3128 |
+
}
|
3129 |
+
|
3130 |
+
/**
|
3131 |
+
* @return array
|
3132 |
+
*/
|
3133 |
+
public function getEvalArguments()
|
3134 |
+
{
|
3135 |
+
$arguments = $this->getArguments();
|
3136 |
+
$arguments[0] = $this->getScript();
|
3137 |
+
|
3138 |
+
return $arguments;
|
3139 |
+
}
|
3140 |
+
}
|
3141 |
+
|
3142 |
+
/**
|
3143 |
+
* @link http://redis.io/commands/bgrewriteaof
|
3144 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3145 |
+
*/
|
3146 |
+
class ServerBackgroundRewriteAOF extends Command
|
3147 |
+
{
|
3148 |
+
/**
|
3149 |
+
* {@inheritdoc}
|
3150 |
+
*/
|
3151 |
+
public function getId()
|
3152 |
+
{
|
3153 |
+
return 'BGREWRITEAOF';
|
3154 |
+
}
|
3155 |
+
|
3156 |
+
/**
|
3157 |
+
* {@inheritdoc}
|
3158 |
+
*/
|
3159 |
+
public function parseResponse($data)
|
3160 |
+
{
|
3161 |
+
return $data == 'Background append only file rewriting started';
|
3162 |
+
}
|
3163 |
+
}
|
3164 |
+
|
3165 |
+
/**
|
3166 |
+
* @link http://redis.io/commands/punsubscribe
|
3167 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3168 |
+
*/
|
3169 |
+
class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
|
3170 |
+
{
|
3171 |
+
/**
|
3172 |
+
* {@inheritdoc}
|
3173 |
+
*/
|
3174 |
+
public function getId()
|
3175 |
+
{
|
3176 |
+
return 'PUNSUBSCRIBE';
|
3177 |
+
}
|
3178 |
+
}
|
3179 |
+
|
3180 |
+
/**
|
3181 |
+
* @link http://redis.io/commands/psubscribe
|
3182 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3183 |
+
*/
|
3184 |
+
class PubSubSubscribeByPattern extends PubSubSubscribe
|
3185 |
+
{
|
3186 |
+
/**
|
3187 |
+
* {@inheritdoc}
|
3188 |
+
*/
|
3189 |
+
public function getId()
|
3190 |
+
{
|
3191 |
+
return 'PSUBSCRIBE';
|
3192 |
+
}
|
3193 |
+
}
|
3194 |
+
|
3195 |
+
/**
|
3196 |
+
* @link http://redis.io/commands/publish
|
3197 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3198 |
+
*/
|
3199 |
+
class PubSubPublish extends Command
|
3200 |
+
{
|
3201 |
+
/**
|
3202 |
+
* {@inheritdoc}
|
3203 |
+
*/
|
3204 |
+
public function getId()
|
3205 |
+
{
|
3206 |
+
return 'PUBLISH';
|
3207 |
+
}
|
3208 |
+
}
|
3209 |
+
|
3210 |
+
/**
|
3211 |
+
* @link http://redis.io/commands/pubsub
|
3212 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3213 |
+
*/
|
3214 |
+
class PubSubPubsub extends Command
|
3215 |
+
{
|
3216 |
+
/**
|
3217 |
+
* {@inheritdoc}
|
3218 |
+
*/
|
3219 |
+
public function getId()
|
3220 |
+
{
|
3221 |
+
return 'PUBSUB';
|
3222 |
+
}
|
3223 |
+
|
3224 |
+
/**
|
3225 |
+
* {@inheritdoc}
|
3226 |
+
*/
|
3227 |
+
public function parseResponse($data)
|
3228 |
+
{
|
3229 |
+
switch (strtolower($this->getArgument(0))) {
|
3230 |
+
case 'numsub':
|
3231 |
+
return self::processNumsub($data);
|
3232 |
+
|
3233 |
+
default:
|
3234 |
+
return $data;
|
3235 |
+
}
|
3236 |
+
}
|
3237 |
+
|
3238 |
+
/**
|
3239 |
+
* Returns the processed response to PUBSUB NUMSUB.
|
3240 |
+
*
|
3241 |
+
* @param array $channels List of channels
|
3242 |
+
*
|
3243 |
+
* @return array
|
3244 |
+
*/
|
3245 |
+
protected static function processNumsub(array $channels)
|
3246 |
+
{
|
3247 |
+
$processed = array();
|
3248 |
+
$count = count($channels);
|
3249 |
+
|
3250 |
+
for ($i = 0; $i < $count; $i++) {
|
3251 |
+
$processed[$channels[$i]] = $channels[++$i];
|
3252 |
+
}
|
3253 |
+
|
3254 |
+
return $processed;
|
3255 |
+
}
|
3256 |
+
}
|
3257 |
+
|
3258 |
+
/**
|
3259 |
+
* @link http://redis.io/commands/bgsave
|
3260 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3261 |
+
*/
|
3262 |
+
class ServerBackgroundSave extends Command
|
3263 |
+
{
|
3264 |
+
/**
|
3265 |
+
* {@inheritdoc}
|
3266 |
+
*/
|
3267 |
+
public function getId()
|
3268 |
+
{
|
3269 |
+
return 'BGSAVE';
|
3270 |
+
}
|
3271 |
+
|
3272 |
+
/**
|
3273 |
+
* {@inheritdoc}
|
3274 |
+
*/
|
3275 |
+
public function parseResponse($data)
|
3276 |
+
{
|
3277 |
+
return $data === 'Background saving started' ? true : $data;
|
3278 |
+
}
|
3279 |
+
}
|
3280 |
+
|
3281 |
+
/**
|
3282 |
+
* @link http://redis.io/commands/client-list
|
3283 |
+
* @link http://redis.io/commands/client-kill
|
3284 |
+
* @link http://redis.io/commands/client-getname
|
3285 |
+
* @link http://redis.io/commands/client-setname
|
3286 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3287 |
+
*/
|
3288 |
+
class ServerClient extends Command
|
3289 |
+
{
|
3290 |
+
/**
|
3291 |
+
* {@inheritdoc}
|
3292 |
+
*/
|
3293 |
+
public function getId()
|
3294 |
+
{
|
3295 |
+
return 'CLIENT';
|
3296 |
+
}
|
3297 |
+
|
3298 |
+
/**
|
3299 |
+
* {@inheritdoc}
|
3300 |
+
*/
|
3301 |
+
public function parseResponse($data)
|
3302 |
+
{
|
3303 |
+
$args = array_change_key_case($this->getArguments(), CASE_UPPER);
|
3304 |
+
|
3305 |
+
switch (strtoupper($args[0])) {
|
3306 |
+
case 'LIST':
|
3307 |
+
return $this->parseClientList($data);
|
3308 |
+
case 'KILL':
|
3309 |
+
case 'GETNAME':
|
3310 |
+
case 'SETNAME':
|
3311 |
+
default:
|
3312 |
+
return $data;
|
3313 |
+
}
|
3314 |
+
}
|
3315 |
+
|
3316 |
+
/**
|
3317 |
+
* Parses the response to CLIENT LIST and returns a structured list.
|
3318 |
+
*
|
3319 |
+
* @param string $data Response buffer.
|
3320 |
+
*
|
3321 |
+
* @return array
|
3322 |
+
*/
|
3323 |
+
protected function parseClientList($data)
|
3324 |
+
{
|
3325 |
+
$clients = array();
|
3326 |
+
|
3327 |
+
foreach (explode("\n", $data, -1) as $clientData) {
|
3328 |
+
$client = array();
|
3329 |
+
|
3330 |
+
foreach (explode(' ', $clientData) as $kv) {
|
3331 |
+
@list($k, $v) = explode('=', $kv);
|
3332 |
+
$client[$k] = $v;
|
3333 |
+
}
|
3334 |
+
|
3335 |
+
$clients[] = $client;
|
3336 |
+
}
|
3337 |
+
|
3338 |
+
return $clients;
|
3339 |
+
}
|
3340 |
+
}
|
3341 |
+
|
3342 |
+
/**
|
3343 |
+
* @link http://redis.io/commands/info
|
3344 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3345 |
+
*/
|
3346 |
+
class ServerInfoV26x extends ServerInfo
|
3347 |
+
{
|
3348 |
+
/**
|
3349 |
+
* {@inheritdoc}
|
3350 |
+
*/
|
3351 |
+
public function parseResponse($data)
|
3352 |
+
{
|
3353 |
+
if ($data === '') {
|
3354 |
+
return array();
|
3355 |
+
}
|
3356 |
+
|
3357 |
+
$info = array();
|
3358 |
+
|
3359 |
+
$current = null;
|
3360 |
+
$infoLines = preg_split('/\r?\n/', $data);
|
3361 |
+
|
3362 |
+
if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
|
3363 |
+
return parent::parseResponse($data);
|
3364 |
+
}
|
3365 |
+
|
3366 |
+
foreach ($infoLines as $row) {
|
3367 |
+
if ($row === '') {
|
3368 |
+
continue;
|
3369 |
+
}
|
3370 |
+
|
3371 |
+
if (preg_match('/^# (\w+)$/', $row, $matches)) {
|
3372 |
+
$info[$matches[1]] = array();
|
3373 |
+
$current = &$info[$matches[1]];
|
3374 |
+
continue;
|
3375 |
+
}
|
3376 |
+
|
3377 |
+
list($k, $v) = $this->parseRow($row);
|
3378 |
+
$current[$k] = $v;
|
3379 |
+
}
|
3380 |
+
|
3381 |
+
return $info;
|
3382 |
+
}
|
3383 |
+
}
|
3384 |
+
|
3385 |
+
/**
|
3386 |
+
* @link http://redis.io/commands/lastsave
|
3387 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3388 |
+
*/
|
3389 |
+
class ServerLastSave extends Command
|
3390 |
+
{
|
3391 |
+
/**
|
3392 |
+
* {@inheritdoc}
|
3393 |
+
*/
|
3394 |
+
public function getId()
|
3395 |
+
{
|
3396 |
+
return 'LASTSAVE';
|
3397 |
+
}
|
3398 |
+
}
|
3399 |
+
|
3400 |
+
/**
|
3401 |
+
* @link http://redis.io/commands/monitor
|
3402 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3403 |
+
*/
|
3404 |
+
class ServerMonitor extends Command
|
3405 |
+
{
|
3406 |
+
/**
|
3407 |
+
* {@inheritdoc}
|
3408 |
+
*/
|
3409 |
+
public function getId()
|
3410 |
+
{
|
3411 |
+
return 'MONITOR';
|
3412 |
+
}
|
3413 |
+
}
|
3414 |
+
|
3415 |
+
/**
|
3416 |
+
* @link http://redis.io/commands/flushdb
|
3417 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3418 |
+
*/
|
3419 |
+
class ServerFlushDatabase extends Command
|
3420 |
+
{
|
3421 |
+
/**
|
3422 |
+
* {@inheritdoc}
|
3423 |
+
*/
|
3424 |
+
public function getId()
|
3425 |
+
{
|
3426 |
+
return 'FLUSHDB';
|
3427 |
+
}
|
3428 |
+
}
|
3429 |
+
|
3430 |
+
/**
|
3431 |
+
* @link http://redis.io/commands/dbsize
|
3432 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3433 |
+
*/
|
3434 |
+
class ServerDatabaseSize extends Command
|
3435 |
+
{
|
3436 |
+
/**
|
3437 |
+
* {@inheritdoc}
|
3438 |
+
*/
|
3439 |
+
public function getId()
|
3440 |
+
{
|
3441 |
+
return 'DBSIZE';
|
3442 |
+
}
|
3443 |
+
}
|
3444 |
+
|
3445 |
+
/**
|
3446 |
+
* @link http://redis.io/commands/command
|
3447 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3448 |
+
*/
|
3449 |
+
class ServerCommand extends Command
|
3450 |
+
{
|
3451 |
+
/**
|
3452 |
+
* {@inheritdoc}
|
3453 |
+
*/
|
3454 |
+
public function getId()
|
3455 |
+
{
|
3456 |
+
return 'COMMAND';
|
3457 |
+
}
|
3458 |
+
}
|
3459 |
+
|
3460 |
+
/**
|
3461 |
+
* @link http://redis.io/commands/config-set
|
3462 |
+
* @link http://redis.io/commands/config-get
|
3463 |
+
* @link http://redis.io/commands/config-resetstat
|
3464 |
+
* @link http://redis.io/commands/config-rewrite
|
3465 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3466 |
+
*/
|
3467 |
+
class ServerConfig extends Command
|
3468 |
+
{
|
3469 |
+
/**
|
3470 |
+
* {@inheritdoc}
|
3471 |
+
*/
|
3472 |
+
public function getId()
|
3473 |
+
{
|
3474 |
+
return 'CONFIG';
|
3475 |
+
}
|
3476 |
+
|
3477 |
+
/**
|
3478 |
+
* {@inheritdoc}
|
3479 |
+
*/
|
3480 |
+
public function parseResponse($data)
|
3481 |
+
{
|
3482 |
+
if (is_array($data)) {
|
3483 |
+
$result = array();
|
3484 |
+
|
3485 |
+
for ($i = 0; $i < count($data); $i++) {
|
3486 |
+
$result[$data[$i]] = $data[++$i];
|
3487 |
+
}
|
3488 |
+
|
3489 |
+
return $result;
|
3490 |
+
}
|
3491 |
+
|
3492 |
+
return $data;
|
3493 |
+
}
|
3494 |
+
}
|
3495 |
+
|
3496 |
+
/**
|
3497 |
+
* Defines a command whose keys can be prefixed.
|
3498 |
+
*
|
3499 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3500 |
+
*/
|
3501 |
+
interface PrefixableCommandInterface extends CommandInterface
|
3502 |
+
{
|
3503 |
+
/**
|
3504 |
+
* Prefixes all the keys found in the arguments of the command.
|
3505 |
+
*
|
3506 |
+
* @param string $prefix String used to prefix the keys.
|
3507 |
+
*/
|
3508 |
+
public function prefixKeys($prefix);
|
3509 |
+
}
|
3510 |
+
|
3511 |
+
/**
|
3512 |
+
* @link http://redis.io/commands/ltrim
|
3513 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3514 |
+
*/
|
3515 |
+
class ListTrim extends Command
|
3516 |
+
{
|
3517 |
+
/**
|
3518 |
+
* {@inheritdoc}
|
3519 |
+
*/
|
3520 |
+
public function getId()
|
3521 |
+
{
|
3522 |
+
return 'LTRIM';
|
3523 |
+
}
|
3524 |
+
}
|
3525 |
+
|
3526 |
+
/**
|
3527 |
+
* @link http://redis.io/commands/lpop
|
3528 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3529 |
+
*/
|
3530 |
+
class ListPopFirst extends Command
|
3531 |
+
{
|
3532 |
+
/**
|
3533 |
+
* {@inheritdoc}
|
3534 |
+
*/
|
3535 |
+
public function getId()
|
3536 |
+
{
|
3537 |
+
return 'LPOP';
|
3538 |
+
}
|
3539 |
+
}
|
3540 |
+
|
3541 |
+
/**
|
3542 |
+
* @link http://redis.io/commands/rpop
|
3543 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3544 |
+
*/
|
3545 |
+
class ListPopLast extends Command
|
3546 |
+
{
|
3547 |
+
/**
|
3548 |
+
* {@inheritdoc}
|
3549 |
+
*/
|
3550 |
+
public function getId()
|
3551 |
+
{
|
3552 |
+
return 'RPOP';
|
3553 |
+
}
|
3554 |
+
}
|
3555 |
+
|
3556 |
+
/**
|
3557 |
+
* @link http://redis.io/commands/brpop
|
3558 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3559 |
+
*/
|
3560 |
+
class ListPopLastBlocking extends ListPopFirstBlocking
|
3561 |
+
{
|
3562 |
+
/**
|
3563 |
+
* {@inheritdoc}
|
3564 |
+
*/
|
3565 |
+
public function getId()
|
3566 |
+
{
|
3567 |
+
return 'BRPOP';
|
3568 |
+
}
|
3569 |
+
}
|
3570 |
+
|
3571 |
+
/**
|
3572 |
+
* @link http://redis.io/commands/llen
|
3573 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3574 |
+
*/
|
3575 |
+
class ListLength extends Command
|
3576 |
+
{
|
3577 |
+
/**
|
3578 |
+
* {@inheritdoc}
|
3579 |
+
*/
|
3580 |
+
public function getId()
|
3581 |
+
{
|
3582 |
+
return 'LLEN';
|
3583 |
+
}
|
3584 |
+
}
|
3585 |
+
|
3586 |
+
/**
|
3587 |
+
* @link http://redis.io/commands/linsert
|
3588 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3589 |
+
*/
|
3590 |
+
class ListInsert extends Command
|
3591 |
+
{
|
3592 |
+
/**
|
3593 |
+
* {@inheritdoc}
|
3594 |
+
*/
|
3595 |
+
public function getId()
|
3596 |
+
{
|
3597 |
+
return 'LINSERT';
|
3598 |
+
}
|
3599 |
+
}
|
3600 |
+
|
3601 |
+
/**
|
3602 |
+
* @link http://redis.io/commands/type
|
3603 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3604 |
+
*/
|
3605 |
+
class KeyType extends Command
|
3606 |
+
{
|
3607 |
+
/**
|
3608 |
+
* {@inheritdoc}
|
3609 |
+
*/
|
3610 |
+
public function getId()
|
3611 |
+
{
|
3612 |
+
return 'TYPE';
|
3613 |
+
}
|
3614 |
+
}
|
3615 |
+
|
3616 |
+
/**
|
3617 |
+
* @link http://redis.io/commands/lindex
|
3618 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3619 |
+
*/
|
3620 |
+
class ListIndex extends Command
|
3621 |
+
{
|
3622 |
+
/**
|
3623 |
+
* {@inheritdoc}
|
3624 |
+
*/
|
3625 |
+
public function getId()
|
3626 |
+
{
|
3627 |
+
return 'LINDEX';
|
3628 |
+
}
|
3629 |
+
}
|
3630 |
+
|
3631 |
+
/**
|
3632 |
+
* @link http://redis.io/commands/rpoplpush
|
3633 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3634 |
+
*/
|
3635 |
+
class ListPopLastPushHead extends Command
|
3636 |
+
{
|
3637 |
+
/**
|
3638 |
+
* {@inheritdoc}
|
3639 |
+
*/
|
3640 |
+
public function getId()
|
3641 |
+
{
|
3642 |
+
return 'RPOPLPUSH';
|
3643 |
+
}
|
3644 |
+
}
|
3645 |
+
|
3646 |
+
/**
|
3647 |
+
* @link http://redis.io/commands/brpoplpush
|
3648 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3649 |
+
*/
|
3650 |
+
class ListPopLastPushHeadBlocking extends Command
|
3651 |
+
{
|
3652 |
+
/**
|
3653 |
+
* {@inheritdoc}
|
3654 |
+
*/
|
3655 |
+
public function getId()
|
3656 |
+
{
|
3657 |
+
return 'BRPOPLPUSH';
|
3658 |
+
}
|
3659 |
+
}
|
3660 |
+
|
3661 |
+
/**
|
3662 |
+
* @link http://redis.io/commands/lrem
|
3663 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3664 |
+
*/
|
3665 |
+
class ListRemove extends Command
|
3666 |
+
{
|
3667 |
+
/**
|
3668 |
+
* {@inheritdoc}
|
3669 |
+
*/
|
3670 |
+
public function getId()
|
3671 |
+
{
|
3672 |
+
return 'LREM';
|
3673 |
+
}
|
3674 |
+
}
|
3675 |
+
|
3676 |
+
/**
|
3677 |
+
* @link http://redis.io/commands/lset
|
3678 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3679 |
+
*/
|
3680 |
+
class ListSet extends Command
|
3681 |
+
{
|
3682 |
+
/**
|
3683 |
+
* {@inheritdoc}
|
3684 |
+
*/
|
3685 |
+
public function getId()
|
3686 |
+
{
|
3687 |
+
return 'LSET';
|
3688 |
+
}
|
3689 |
+
}
|
3690 |
+
|
3691 |
+
/**
|
3692 |
+
* @link http://redis.io/commands/lrange
|
3693 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3694 |
+
*/
|
3695 |
+
class ListRange extends Command
|
3696 |
+
{
|
3697 |
+
/**
|
3698 |
+
* {@inheritdoc}
|
3699 |
+
*/
|
3700 |
+
public function getId()
|
3701 |
+
{
|
3702 |
+
return 'LRANGE';
|
3703 |
+
}
|
3704 |
+
}
|
3705 |
+
|
3706 |
+
/**
|
3707 |
+
* @link http://redis.io/commands/rpushx
|
3708 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3709 |
+
*/
|
3710 |
+
class ListPushTailX extends Command
|
3711 |
+
{
|
3712 |
+
/**
|
3713 |
+
* {@inheritdoc}
|
3714 |
+
*/
|
3715 |
+
public function getId()
|
3716 |
+
{
|
3717 |
+
return 'RPUSHX';
|
3718 |
+
}
|
3719 |
+
}
|
3720 |
+
|
3721 |
+
/**
|
3722 |
+
* @link http://redis.io/commands/lpush
|
3723 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3724 |
+
*/
|
3725 |
+
class ListPushHead extends ListPushTail
|
3726 |
+
{
|
3727 |
+
/**
|
3728 |
+
* {@inheritdoc}
|
3729 |
+
*/
|
3730 |
+
public function getId()
|
3731 |
+
{
|
3732 |
+
return 'LPUSH';
|
3733 |
+
}
|
3734 |
+
}
|
3735 |
+
|
3736 |
+
/**
|
3737 |
+
* @link http://redis.io/commands/lpushx
|
3738 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3739 |
+
*/
|
3740 |
+
class ListPushHeadX extends Command
|
3741 |
+
{
|
3742 |
+
/**
|
3743 |
+
* {@inheritdoc}
|
3744 |
+
*/
|
3745 |
+
public function getId()
|
3746 |
+
{
|
3747 |
+
return 'LPUSHX';
|
3748 |
+
}
|
3749 |
+
}
|
3750 |
+
|
3751 |
+
/**
|
3752 |
+
* @link http://redis.io/commands/object
|
3753 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3754 |
+
*/
|
3755 |
+
class ServerObject extends Command
|
3756 |
+
{
|
3757 |
+
/**
|
3758 |
+
* {@inheritdoc}
|
3759 |
+
*/
|
3760 |
+
public function getId()
|
3761 |
+
{
|
3762 |
+
return 'OBJECT';
|
3763 |
+
}
|
3764 |
+
}
|
3765 |
+
|
3766 |
+
/* --------------------------------------------------------------------------- */
|
3767 |
+
|
3768 |
+
namespace Predis\Connection;
|
3769 |
+
|
3770 |
+
use InvalidArgumentException;
|
3771 |
+
use Predis\CommunicationException;
|
3772 |
+
use Predis\Command\CommandInterface;
|
3773 |
+
use Predis\Protocol\ProtocolException;
|
3774 |
+
use Predis\Protocol\ProtocolProcessorInterface;
|
3775 |
+
use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor;
|
3776 |
+
use UnexpectedValueException;
|
3777 |
+
use ReflectionClass;
|
3778 |
+
use Predis\Command\RawCommand;
|
3779 |
+
use Predis\NotSupportedException;
|
3780 |
+
use Predis\Response\Error as ErrorResponse;
|
3781 |
+
use Predis\Response\Status as StatusResponse;
|
3782 |
+
|
3783 |
+
/**
|
3784 |
+
* Defines a connection object used to communicate with one or multiple
|
3785 |
+
* Redis servers.
|
3786 |
+
*
|
3787 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3788 |
+
*/
|
3789 |
+
interface ConnectionInterface
|
3790 |
+
{
|
3791 |
+
/**
|
3792 |
+
* Opens the connection to Redis.
|
3793 |
+
*/
|
3794 |
+
public function connect();
|
3795 |
+
|
3796 |
+
/**
|
3797 |
+
* Closes the connection to Redis.
|
3798 |
+
*/
|
3799 |
+
public function disconnect();
|
3800 |
+
|
3801 |
+
/**
|
3802 |
+
* Checks if the connection to Redis is considered open.
|
3803 |
+
*
|
3804 |
+
* @return bool
|
3805 |
+
*/
|
3806 |
+
public function isConnected();
|
3807 |
+
|
3808 |
+
/**
|
3809 |
+
* Writes the request for the given command over the connection.
|
3810 |
+
*
|
3811 |
+
* @param CommandInterface $command Command instance.
|
3812 |
+
*/
|
3813 |
+
public function writeRequest(CommandInterface $command);
|
3814 |
+
|
3815 |
+
/**
|
3816 |
+
* Reads the response to the given command from the connection.
|
3817 |
+
*
|
3818 |
+
* @param CommandInterface $command Command instance.
|
3819 |
+
*
|
3820 |
+
* @return mixed
|
3821 |
+
*/
|
3822 |
+
public function readResponse(CommandInterface $command);
|
3823 |
+
|
3824 |
+
/**
|
3825 |
+
* Writes a request for the given command over the connection and reads back
|
3826 |
+
* the response returned by Redis.
|
3827 |
+
*
|
3828 |
+
* @param CommandInterface $command Command instance.
|
3829 |
+
*
|
3830 |
+
* @return mixed
|
3831 |
+
*/
|
3832 |
+
public function executeCommand(CommandInterface $command);
|
3833 |
+
}
|
3834 |
+
|
3835 |
+
/**
|
3836 |
+
* Defines a connection used to communicate with a single Redis node.
|
3837 |
+
*
|
3838 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3839 |
+
*/
|
3840 |
+
interface NodeConnectionInterface extends ConnectionInterface
|
3841 |
+
{
|
3842 |
+
/**
|
3843 |
+
* Returns a string representation of the connection.
|
3844 |
+
*
|
3845 |
+
* @return string
|
3846 |
+
*/
|
3847 |
+
public function __toString();
|
3848 |
+
|
3849 |
+
/**
|
3850 |
+
* Returns the underlying resource used to communicate with Redis.
|
3851 |
+
*
|
3852 |
+
* @return mixed
|
3853 |
+
*/
|
3854 |
+
public function getResource();
|
3855 |
+
|
3856 |
+
/**
|
3857 |
+
* Returns the parameters used to initialize the connection.
|
3858 |
+
*
|
3859 |
+
* @return ParametersInterface
|
3860 |
+
*/
|
3861 |
+
public function getParameters();
|
3862 |
+
|
3863 |
+
/**
|
3864 |
+
* Pushes the given command into a queue of commands executed when
|
3865 |
+
* establishing the actual connection to Redis.
|
3866 |
+
*
|
3867 |
+
* @param CommandInterface $command Instance of a Redis command.
|
3868 |
+
*/
|
3869 |
+
public function addConnectCommand(CommandInterface $command);
|
3870 |
+
|
3871 |
+
/**
|
3872 |
+
* Reads a response from the server.
|
3873 |
+
*
|
3874 |
+
* @return mixed
|
3875 |
+
*/
|
3876 |
+
public function read();
|
3877 |
+
}
|
3878 |
+
|
3879 |
+
/**
|
3880 |
+
* Defines a virtual connection composed of multiple connection instances to
|
3881 |
+
* single Redis nodes.
|
3882 |
+
*
|
3883 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3884 |
+
*/
|
3885 |
+
interface AggregateConnectionInterface extends ConnectionInterface
|
3886 |
+
{
|
3887 |
+
/**
|
3888 |
+
* Adds a connection instance to the aggregate connection.
|
3889 |
+
*
|
3890 |
+
* @param NodeConnectionInterface $connection Connection instance.
|
3891 |
+
*/
|
3892 |
+
public function add(NodeConnectionInterface $connection);
|
3893 |
+
|
3894 |
+
/**
|
3895 |
+
* Removes the specified connection instance from the aggregate connection.
|
3896 |
+
*
|
3897 |
+
* @param NodeConnectionInterface $connection Connection instance.
|
3898 |
+
*
|
3899 |
+
* @return bool Returns true if the connection was in the pool.
|
3900 |
+
*/
|
3901 |
+
public function remove(NodeConnectionInterface $connection);
|
3902 |
+
|
3903 |
+
/**
|
3904 |
+
* Returns the connection instance in charge for the given command.
|
3905 |
+
*
|
3906 |
+
* @param CommandInterface $command Command instance.
|
3907 |
+
*
|
3908 |
+
* @return NodeConnectionInterface
|
3909 |
+
*/
|
3910 |
+
public function getConnection(CommandInterface $command);
|
3911 |
+
|
3912 |
+
/**
|
3913 |
+
* Returns a connection instance from the aggregate connection by its alias.
|
3914 |
+
*
|
3915 |
+
* @param string $connectionID Connection alias.
|
3916 |
+
*
|
3917 |
+
* @return NodeConnectionInterface|null
|
3918 |
+
*/
|
3919 |
+
public function getConnectionById($connectionID);
|
3920 |
+
}
|
3921 |
+
|
3922 |
+
/**
|
3923 |
+
* Base class with the common logic used by connection classes to communicate
|
3924 |
+
* with Redis.
|
3925 |
+
*
|
3926 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
3927 |
+
*/
|
3928 |
+
abstract class AbstractConnection implements NodeConnectionInterface
|
3929 |
+
{
|
3930 |
+
private $resource;
|
3931 |
+
private $cachedId;
|
3932 |
+
|
3933 |
+
protected $parameters;
|
3934 |
+
protected $initCommands = array();
|
3935 |
+
|
3936 |
+
/**
|
3937 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
3938 |
+
*/
|
3939 |
+
public function __construct(ParametersInterface $parameters)
|
3940 |
+
{
|
3941 |
+
$this->parameters = $this->assertParameters($parameters);
|
3942 |
+
}
|
3943 |
+
|
3944 |
+
/**
|
3945 |
+
* Disconnects from the server and destroys the underlying resource when
|
3946 |
+
* PHP's garbage collector kicks in.
|
3947 |
+
*/
|
3948 |
+
public function __destruct()
|
3949 |
+
{
|
3950 |
+
$this->disconnect();
|
3951 |
+
}
|
3952 |
+
|
3953 |
+
/**
|
3954 |
+
* Checks some of the parameters used to initialize the connection.
|
3955 |
+
*
|
3956 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
3957 |
+
*
|
3958 |
+
* @return ParametersInterface
|
3959 |
+
*
|
3960 |
+
* @throws \InvalidArgumentException
|
3961 |
+
*/
|
3962 |
+
protected function assertParameters(ParametersInterface $parameters)
|
3963 |
+
{
|
3964 |
+
$scheme = $parameters->scheme;
|
3965 |
+
|
3966 |
+
if ($scheme !== 'tcp' && $scheme !== 'unix') {
|
3967 |
+
throw new InvalidArgumentException("Invalid scheme: '$scheme'.");
|
3968 |
+
}
|
3969 |
+
|
3970 |
+
if ($scheme === 'unix' && !isset($parameters->path)) {
|
3971 |
+
throw new InvalidArgumentException('Missing UNIX domain socket path.');
|
3972 |
+
}
|
3973 |
+
|
3974 |
+
return $parameters;
|
3975 |
+
}
|
3976 |
+
|
3977 |
+
/**
|
3978 |
+
* Creates the underlying resource used to communicate with Redis.
|
3979 |
+
*
|
3980 |
+
* @return mixed
|
3981 |
+
*/
|
3982 |
+
abstract protected function createResource();
|
3983 |
+
|
3984 |
+
/**
|
3985 |
+
* {@inheritdoc}
|
3986 |
+
*/
|
3987 |
+
public function isConnected()
|
3988 |
+
{
|
3989 |
+
return isset($this->resource);
|
3990 |
+
}
|
3991 |
+
|
3992 |
+
/**
|
3993 |
+
* {@inheritdoc}
|
3994 |
+
*/
|
3995 |
+
public function connect()
|
3996 |
+
{
|
3997 |
+
if (!$this->isConnected()) {
|
3998 |
+
$this->resource = $this->createResource();
|
3999 |
+
|
4000 |
+
return true;
|
4001 |
+
}
|
4002 |
+
|
4003 |
+
return false;
|
4004 |
+
}
|
4005 |
+
|
4006 |
+
/**
|
4007 |
+
* {@inheritdoc}
|
4008 |
+
*/
|
4009 |
+
public function disconnect()
|
4010 |
+
{
|
4011 |
+
unset($this->resource);
|
4012 |
+
}
|
4013 |
+
|
4014 |
+
/**
|
4015 |
+
* {@inheritdoc}
|
4016 |
+
*/
|
4017 |
+
public function addConnectCommand(CommandInterface $command)
|
4018 |
+
{
|
4019 |
+
$this->initCommands[] = $command;
|
4020 |
+
}
|
4021 |
+
|
4022 |
+
/**
|
4023 |
+
* {@inheritdoc}
|
4024 |
+
*/
|
4025 |
+
public function executeCommand(CommandInterface $command)
|
4026 |
+
{
|
4027 |
+
$this->writeRequest($command);
|
4028 |
+
|
4029 |
+
return $this->readResponse($command);
|
4030 |
+
}
|
4031 |
+
|
4032 |
+
/**
|
4033 |
+
* {@inheritdoc}
|
4034 |
+
*/
|
4035 |
+
public function readResponse(CommandInterface $command)
|
4036 |
+
{
|
4037 |
+
return $this->read();
|
4038 |
+
}
|
4039 |
+
|
4040 |
+
/**
|
4041 |
+
* Helper method to handle connection errors.
|
4042 |
+
*
|
4043 |
+
* @param string $message Error message.
|
4044 |
+
* @param int $code Error code.
|
4045 |
+
*/
|
4046 |
+
protected function onConnectionError($message, $code = null)
|
4047 |
+
{
|
4048 |
+
CommunicationException::handle(
|
4049 |
+
new ConnectionException(
|
4050 |
+
$this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]", $code
|
4051 |
+
)
|
4052 |
+
);
|
4053 |
+
}
|
4054 |
+
|
4055 |
+
/**
|
4056 |
+
* Helper method to handle protocol errors.
|
4057 |
+
*
|
4058 |
+
* @param string $message Error message.
|
4059 |
+
*/
|
4060 |
+
protected function onProtocolError($message)
|
4061 |
+
{
|
4062 |
+
CommunicationException::handle(
|
4063 |
+
new ProtocolException(
|
4064 |
+
$this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]"
|
4065 |
+
)
|
4066 |
+
);
|
4067 |
+
}
|
4068 |
+
|
4069 |
+
/**
|
4070 |
+
* {@inheritdoc}
|
4071 |
+
*/
|
4072 |
+
public function getResource()
|
4073 |
+
{
|
4074 |
+
if (isset($this->resource)) {
|
4075 |
+
return $this->resource;
|
4076 |
+
}
|
4077 |
+
|
4078 |
+
$this->connect();
|
4079 |
+
|
4080 |
+
return $this->resource;
|
4081 |
+
}
|
4082 |
+
|
4083 |
+
/**
|
4084 |
+
* {@inheritdoc}
|
4085 |
+
*/
|
4086 |
+
public function getParameters()
|
4087 |
+
{
|
4088 |
+
return $this->parameters;
|
4089 |
+
}
|
4090 |
+
|
4091 |
+
/**
|
4092 |
+
* Gets an identifier for the connection.
|
4093 |
+
*
|
4094 |
+
* @return string
|
4095 |
+
*/
|
4096 |
+
protected function getIdentifier()
|
4097 |
+
{
|
4098 |
+
if ($this->parameters->scheme === 'unix') {
|
4099 |
+
return $this->parameters->path;
|
4100 |
+
}
|
4101 |
+
|
4102 |
+
return "{$this->parameters->host}:{$this->parameters->port}";
|
4103 |
+
}
|
4104 |
+
|
4105 |
+
/**
|
4106 |
+
* {@inheritdoc}
|
4107 |
+
*/
|
4108 |
+
public function __toString()
|
4109 |
+
{
|
4110 |
+
if (!isset($this->cachedId)) {
|
4111 |
+
$this->cachedId = $this->getIdentifier();
|
4112 |
+
}
|
4113 |
+
|
4114 |
+
return $this->cachedId;
|
4115 |
+
}
|
4116 |
+
|
4117 |
+
/**
|
4118 |
+
* {@inheritdoc}
|
4119 |
+
*/
|
4120 |
+
public function __sleep()
|
4121 |
+
{
|
4122 |
+
return array('parameters', 'initCommands');
|
4123 |
+
}
|
4124 |
+
}
|
4125 |
+
|
4126 |
+
/**
|
4127 |
+
* Standard connection to Redis servers implemented on top of PHP's streams.
|
4128 |
+
* The connection parameters supported by this class are:
|
4129 |
+
*
|
4130 |
+
* - scheme: it can be either 'tcp' or 'unix'.
|
4131 |
+
* - host: hostname or IP address of the server.
|
4132 |
+
* - port: TCP port of the server.
|
4133 |
+
* - path: path of a UNIX domain socket when scheme is 'unix'.
|
4134 |
+
* - timeout: timeout to perform the connection.
|
4135 |
+
* - read_write_timeout: timeout of read / write operations.
|
4136 |
+
* - async_connect: performs the connection asynchronously.
|
4137 |
+
* - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
|
4138 |
+
* - persistent: the connection is left intact after a GC collection.
|
4139 |
+
*
|
4140 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4141 |
+
*/
|
4142 |
+
class StreamConnection extends AbstractConnection
|
4143 |
+
{
|
4144 |
+
/**
|
4145 |
+
* Disconnects from the server and destroys the underlying resource when the
|
4146 |
+
* garbage collector kicks in only if the connection has not been marked as
|
4147 |
+
* persistent.
|
4148 |
+
*/
|
4149 |
+
public function __destruct()
|
4150 |
+
{
|
4151 |
+
if (isset($this->parameters->persistent) && $this->parameters->persistent) {
|
4152 |
+
return;
|
4153 |
+
}
|
4154 |
+
|
4155 |
+
$this->disconnect();
|
4156 |
+
}
|
4157 |
+
|
4158 |
+
/**
|
4159 |
+
* {@inheritdoc}
|
4160 |
+
*/
|
4161 |
+
protected function createResource()
|
4162 |
+
{
|
4163 |
+
$initializer = "{$this->parameters->scheme}StreamInitializer";
|
4164 |
+
$resource = $this->$initializer($this->parameters);
|
4165 |
+
|
4166 |
+
return $resource;
|
4167 |
+
}
|
4168 |
+
|
4169 |
+
/**
|
4170 |
+
* Initializes a TCP stream resource.
|
4171 |
+
*
|
4172 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
4173 |
+
*
|
4174 |
+
* @return resource
|
4175 |
+
*/
|
4176 |
+
protected function tcpStreamInitializer(ParametersInterface $parameters)
|
4177 |
+
{
|
4178 |
+
$uri = "tcp://{$parameters->host}:{$parameters->port}";
|
4179 |
+
$flags = STREAM_CLIENT_CONNECT;
|
4180 |
+
|
4181 |
+
if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
|
4182 |
+
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
|
4183 |
+
}
|
4184 |
+
|
4185 |
+
if (isset($parameters->persistent) && (bool) $parameters->persistent) {
|
4186 |
+
$flags |= STREAM_CLIENT_PERSISTENT;
|
4187 |
+
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
|
4188 |
+
}
|
4189 |
+
|
4190 |
+
$resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
|
4191 |
+
|
4192 |
+
if (!$resource) {
|
4193 |
+
$this->onConnectionError(trim($errstr), $errno);
|
4194 |
+
}
|
4195 |
+
|
4196 |
+
if (isset($parameters->read_write_timeout)) {
|
4197 |
+
$rwtimeout = (float) $parameters->read_write_timeout;
|
4198 |
+
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
|
4199 |
+
$timeoutSeconds = floor($rwtimeout);
|
4200 |
+
$timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
|
4201 |
+
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
|
4202 |
+
}
|
4203 |
+
|
4204 |
+
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
|
4205 |
+
$socket = socket_import_stream($resource);
|
4206 |
+
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
|
4207 |
+
}
|
4208 |
+
|
4209 |
+
return $resource;
|
4210 |
+
}
|
4211 |
+
|
4212 |
+
/**
|
4213 |
+
* Initializes a UNIX stream resource.
|
4214 |
+
*
|
4215 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
4216 |
+
*
|
4217 |
+
* @return resource
|
4218 |
+
*/
|
4219 |
+
protected function unixStreamInitializer(ParametersInterface $parameters)
|
4220 |
+
{
|
4221 |
+
$uri = "unix://{$parameters->path}";
|
4222 |
+
$flags = STREAM_CLIENT_CONNECT;
|
4223 |
+
|
4224 |
+
if ((bool) $parameters->persistent) {
|
4225 |
+
$flags |= STREAM_CLIENT_PERSISTENT;
|
4226 |
+
}
|
4227 |
+
|
4228 |
+
$resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
|
4229 |
+
|
4230 |
+
if (!$resource) {
|
4231 |
+
$this->onConnectionError(trim($errstr), $errno);
|
4232 |
+
}
|
4233 |
+
|
4234 |
+
if (isset($parameters->read_write_timeout)) {
|
4235 |
+
$rwtimeout = (float) $parameters->read_write_timeout;
|
4236 |
+
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
|
4237 |
+
$timeoutSeconds = floor($rwtimeout);
|
4238 |
+
$timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
|
4239 |
+
stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
|
4240 |
+
}
|
4241 |
+
|
4242 |
+
return $resource;
|
4243 |
+
}
|
4244 |
+
|
4245 |
+
/**
|
4246 |
+
* {@inheritdoc}
|
4247 |
+
*/
|
4248 |
+
public function connect()
|
4249 |
+
{
|
4250 |
+
if (parent::connect() && $this->initCommands) {
|
4251 |
+
foreach ($this->initCommands as $command) {
|
4252 |
+
$this->executeCommand($command);
|
4253 |
+
}
|
4254 |
+
}
|
4255 |
+
}
|
4256 |
+
|
4257 |
+
/**
|
4258 |
+
* {@inheritdoc}
|
4259 |
+
*/
|
4260 |
+
public function disconnect()
|
4261 |
+
{
|
4262 |
+
if ($this->isConnected()) {
|
4263 |
+
fclose($this->getResource());
|
4264 |
+
parent::disconnect();
|
4265 |
+
}
|
4266 |
+
}
|
4267 |
+
|
4268 |
+
/**
|
4269 |
+
* Performs a write operation over the stream of the buffer containing a
|
4270 |
+
* command serialized with the Redis wire protocol.
|
4271 |
+
*
|
4272 |
+
* @param string $buffer Representation of a command in the Redis wire protocol.
|
4273 |
+
*/
|
4274 |
+
protected function write($buffer)
|
4275 |
+
{
|
4276 |
+
$socket = $this->getResource();
|
4277 |
+
|
4278 |
+
while (($length = strlen($buffer)) > 0) {
|
4279 |
+
$written = @fwrite($socket, $buffer);
|
4280 |
+
|
4281 |
+
if ($length === $written) {
|
4282 |
+
return;
|
4283 |
+
}
|
4284 |
+
|
4285 |
+
if ($written === false || $written === 0) {
|
4286 |
+
$this->onConnectionError('Error while writing bytes to the server.');
|
4287 |
+
}
|
4288 |
+
|
4289 |
+
$buffer = substr($buffer, $written);
|
4290 |
+
}
|
4291 |
+
}
|
4292 |
+
|
4293 |
+
/**
|
4294 |
+
* {@inheritdoc}
|
4295 |
+
*/
|
4296 |
+
public function read()
|
4297 |
+
{
|
4298 |
+
$socket = $this->getResource();
|
4299 |
+
$chunk = fgets($socket);
|
4300 |
+
|
4301 |
+
if ($chunk === false || $chunk === '') {
|
4302 |
+
$this->onConnectionError('Error while reading line from the server.');
|
4303 |
+
}
|
4304 |
+
|
4305 |
+
$prefix = $chunk[0];
|
4306 |
+
$payload = substr($chunk, 1, -2);
|
4307 |
+
|
4308 |
+
switch ($prefix) {
|
4309 |
+
case '+':
|
4310 |
+
return StatusResponse::get($payload);
|
4311 |
+
|
4312 |
+
case '$':
|
4313 |
+
$size = (int) $payload;
|
4314 |
+
|
4315 |
+
if ($size === -1) {
|
4316 |
+
return null;
|
4317 |
+
}
|
4318 |
+
|
4319 |
+
$bulkData = '';
|
4320 |
+
$bytesLeft = ($size += 2);
|
4321 |
+
|
4322 |
+
do {
|
4323 |
+
$chunk = fread($socket, min($bytesLeft, 4096));
|
4324 |
+
|
4325 |
+
if ($chunk === false || $chunk === '') {
|
4326 |
+
$this->onConnectionError('Error while reading bytes from the server.');
|
4327 |
+
}
|
4328 |
+
|
4329 |
+
$bulkData .= $chunk;
|
4330 |
+
$bytesLeft = $size - strlen($bulkData);
|
4331 |
+
} while ($bytesLeft > 0);
|
4332 |
+
|
4333 |
+
return substr($bulkData, 0, -2);
|
4334 |
+
|
4335 |
+
case '*':
|
4336 |
+
$count = (int) $payload;
|
4337 |
+
|
4338 |
+
if ($count === -1) {
|
4339 |
+
return null;
|
4340 |
+
}
|
4341 |
+
|
4342 |
+
$multibulk = array();
|
4343 |
+
|
4344 |
+
for ($i = 0; $i < $count; $i++) {
|
4345 |
+
$multibulk[$i] = $this->read();
|
4346 |
+
}
|
4347 |
+
|
4348 |
+
return $multibulk;
|
4349 |
+
|
4350 |
+
case ':':
|
4351 |
+
return (int) $payload;
|
4352 |
+
|
4353 |
+
case '-':
|
4354 |
+
return new ErrorResponse($payload);
|
4355 |
+
|
4356 |
+
default:
|
4357 |
+
$this->onProtocolError("Unknown response prefix: '$prefix'.");
|
4358 |
+
|
4359 |
+
return;
|
4360 |
+
}
|
4361 |
+
}
|
4362 |
+
|
4363 |
+
/**
|
4364 |
+
* {@inheritdoc}
|
4365 |
+
*/
|
4366 |
+
public function writeRequest(CommandInterface $command)
|
4367 |
+
{
|
4368 |
+
$commandID = $command->getId();
|
4369 |
+
$arguments = $command->getArguments();
|
4370 |
+
|
4371 |
+
$cmdlen = strlen($commandID);
|
4372 |
+
$reqlen = count($arguments) + 1;
|
4373 |
+
|
4374 |
+
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
4375 |
+
|
4376 |
+
for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
|
4377 |
+
$argument = $arguments[$i];
|
4378 |
+
$arglen = strlen($argument);
|
4379 |
+
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
4380 |
+
}
|
4381 |
+
|
4382 |
+
$this->write($buffer);
|
4383 |
+
}
|
4384 |
+
}
|
4385 |
+
|
4386 |
+
/**
|
4387 |
+
* Interface defining a container for connection parameters.
|
4388 |
+
*
|
4389 |
+
* The actual list of connection parameters depends on the features supported by
|
4390 |
+
* each connection backend class (please refer to their specific documentation),
|
4391 |
+
* but the most common parameters used through the library are:
|
4392 |
+
*
|
4393 |
+
* @property-read string scheme Connection scheme, such as 'tcp' or 'unix'.
|
4394 |
+
* @property-read string host IP address or hostname of Redis.
|
4395 |
+
* @property-read int port TCP port on which Redis is listening to.
|
4396 |
+
* @property-read string path Path of a UNIX domain socket file.
|
4397 |
+
* @property-read string alias Alias for the connection.
|
4398 |
+
* @property-read float timeout Timeout for the connect() operation.
|
4399 |
+
* @property-read float read_write_timeout Timeout for read() and write() operations.
|
4400 |
+
* @property-read bool async_connect Performs the connect() operation asynchronously.
|
4401 |
+
* @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing.
|
4402 |
+
* @property-read bool persistent Leaves the connection open after a GC collection.
|
4403 |
+
* @property-read string password Password to access Redis (see the AUTH command).
|
4404 |
+
* @property-read string database Database index (see the SELECT command).
|
4405 |
+
*
|
4406 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4407 |
+
*/
|
4408 |
+
interface ParametersInterface
|
4409 |
+
{
|
4410 |
+
/**
|
4411 |
+
* Checks if the specified parameters is set.
|
4412 |
+
*
|
4413 |
+
* @param string $parameter Name of the parameter.
|
4414 |
+
*
|
4415 |
+
* @return bool
|
4416 |
+
*/
|
4417 |
+
public function __isset($parameter);
|
4418 |
+
|
4419 |
+
/**
|
4420 |
+
* Returns the value of the specified parameter.
|
4421 |
+
*
|
4422 |
+
* @param string $parameter Name of the parameter.
|
4423 |
+
*
|
4424 |
+
* @return mixed|null
|
4425 |
+
*/
|
4426 |
+
public function __get($parameter);
|
4427 |
+
|
4428 |
+
/**
|
4429 |
+
* Returns an array representation of the connection parameters.
|
4430 |
+
*
|
4431 |
+
* @return array
|
4432 |
+
*/
|
4433 |
+
public function toArray();
|
4434 |
+
}
|
4435 |
+
|
4436 |
+
/**
|
4437 |
+
* Interface for classes providing a factory of connections to Redis nodes.
|
4438 |
+
*
|
4439 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4440 |
+
*/
|
4441 |
+
interface FactoryInterface
|
4442 |
+
{
|
4443 |
+
/**
|
4444 |
+
* Defines or overrides the connection class identified by a scheme prefix.
|
4445 |
+
*
|
4446 |
+
* @param string $scheme Target connection scheme.
|
4447 |
+
* @param mixed $initializer Fully-qualified name of a class or a callable for lazy initialization.
|
4448 |
+
*/
|
4449 |
+
public function define($scheme, $initializer);
|
4450 |
+
|
4451 |
+
/**
|
4452 |
+
* Undefines the connection identified by a scheme prefix.
|
4453 |
+
*
|
4454 |
+
* @param string $scheme Target connection scheme.
|
4455 |
+
*/
|
4456 |
+
public function undefine($scheme);
|
4457 |
+
|
4458 |
+
/**
|
4459 |
+
* Creates a new connection object.
|
4460 |
+
*
|
4461 |
+
* @param mixed $parameters Initialization parameters for the connection.
|
4462 |
+
*
|
4463 |
+
* @return NodeConnectionInterface
|
4464 |
+
*/
|
4465 |
+
public function create($parameters);
|
4466 |
+
|
4467 |
+
/**
|
4468 |
+
* Aggregates single connections into an aggregate connection instance.
|
4469 |
+
*
|
4470 |
+
* @param AggregateConnectionInterface $aggregate Aggregate connection instance.
|
4471 |
+
* @param array $parameters List of parameters for each connection.
|
4472 |
+
*/
|
4473 |
+
public function aggregate(AggregateConnectionInterface $aggregate, array $parameters);
|
4474 |
+
}
|
4475 |
+
|
4476 |
+
/**
|
4477 |
+
* Defines a connection to communicate with a single Redis server that leverages
|
4478 |
+
* an external protocol processor to handle pluggable protocol handlers.
|
4479 |
+
*
|
4480 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4481 |
+
*/
|
4482 |
+
interface CompositeConnectionInterface extends NodeConnectionInterface
|
4483 |
+
{
|
4484 |
+
/**
|
4485 |
+
* Returns the protocol processor used by the connection.
|
4486 |
+
*/
|
4487 |
+
public function getProtocol();
|
4488 |
+
|
4489 |
+
/**
|
4490 |
+
* Writes the buffer containing over the connection.
|
4491 |
+
*
|
4492 |
+
* @param string $buffer String buffer to be sent over the connection.
|
4493 |
+
*/
|
4494 |
+
public function writeBuffer($buffer);
|
4495 |
+
|
4496 |
+
/**
|
4497 |
+
* Reads the given number of bytes from the connection.
|
4498 |
+
*
|
4499 |
+
* @param int $length Number of bytes to read from the connection.
|
4500 |
+
*
|
4501 |
+
* @return string
|
4502 |
+
*/
|
4503 |
+
public function readBuffer($length);
|
4504 |
+
|
4505 |
+
/**
|
4506 |
+
* Reads a line from the connection.
|
4507 |
+
*
|
4508 |
+
* @param string
|
4509 |
+
*/
|
4510 |
+
public function readLine();
|
4511 |
+
}
|
4512 |
+
|
4513 |
+
/**
|
4514 |
+
* This class provides the implementation of a Predis connection that uses PHP's
|
4515 |
+
* streams for network communication and wraps the phpiredis C extension (PHP
|
4516 |
+
* bindings for hiredis) to parse and serialize the Redis protocol.
|
4517 |
+
*
|
4518 |
+
* This class is intended to provide an optional low-overhead alternative for
|
4519 |
+
* processing responses from Redis compared to the standard pure-PHP classes.
|
4520 |
+
* Differences in speed when dealing with short inline responses are practically
|
4521 |
+
* nonexistent, the actual speed boost is for big multibulk responses when this
|
4522 |
+
* protocol processor can parse and return responses very fast.
|
4523 |
+
*
|
4524 |
+
* For instructions on how to build and install the phpiredis extension, please
|
4525 |
+
* consult the repository of the project.
|
4526 |
+
*
|
4527 |
+
* The connection parameters supported by this class are:
|
4528 |
+
*
|
4529 |
+
* - scheme: it can be either 'tcp' or 'unix'.
|
4530 |
+
* - host: hostname or IP address of the server.
|
4531 |
+
* - port: TCP port of the server.
|
4532 |
+
* - path: path of a UNIX domain socket when scheme is 'unix'.
|
4533 |
+
* - timeout: timeout to perform the connection.
|
4534 |
+
* - read_write_timeout: timeout of read / write operations.
|
4535 |
+
* - async_connect: performs the connection asynchronously.
|
4536 |
+
* - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
|
4537 |
+
* - persistent: the connection is left intact after a GC collection.
|
4538 |
+
*
|
4539 |
+
* @link https://github.com/nrk/phpiredis
|
4540 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4541 |
+
*/
|
4542 |
+
class PhpiredisStreamConnection extends StreamConnection
|
4543 |
+
{
|
4544 |
+
private $reader;
|
4545 |
+
|
4546 |
+
/**
|
4547 |
+
* {@inheritdoc}
|
4548 |
+
*/
|
4549 |
+
public function __construct(ParametersInterface $parameters)
|
4550 |
+
{
|
4551 |
+
$this->assertExtensions();
|
4552 |
+
|
4553 |
+
parent::__construct($parameters);
|
4554 |
+
|
4555 |
+
$this->reader = $this->createReader();
|
4556 |
+
}
|
4557 |
+
|
4558 |
+
/**
|
4559 |
+
* {@inheritdoc}
|
4560 |
+
*/
|
4561 |
+
public function __destruct()
|
4562 |
+
{
|
4563 |
+
phpiredis_reader_destroy($this->reader);
|
4564 |
+
|
4565 |
+
parent::__destruct();
|
4566 |
+
}
|
4567 |
+
|
4568 |
+
/**
|
4569 |
+
* Checks if the phpiredis extension is loaded in PHP.
|
4570 |
+
*/
|
4571 |
+
private function assertExtensions()
|
4572 |
+
{
|
4573 |
+
if (!extension_loaded('phpiredis')) {
|
4574 |
+
throw new NotSupportedException(
|
4575 |
+
'The "phpiredis" extension is required by this connection backend.'
|
4576 |
+
);
|
4577 |
+
}
|
4578 |
+
}
|
4579 |
+
|
4580 |
+
/**
|
4581 |
+
* {@inheritdoc}
|
4582 |
+
*/
|
4583 |
+
protected function tcpStreamInitializer(ParametersInterface $parameters)
|
4584 |
+
{
|
4585 |
+
$uri = "tcp://{$parameters->host}:{$parameters->port}";
|
4586 |
+
$flags = STREAM_CLIENT_CONNECT;
|
4587 |
+
$socket = null;
|
4588 |
+
|
4589 |
+
if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
|
4590 |
+
$flags |= STREAM_CLIENT_ASYNC_CONNECT;
|
4591 |
+
}
|
4592 |
+
|
4593 |
+
if (isset($parameters->persistent) && (bool) $parameters->persistent) {
|
4594 |
+
$flags |= STREAM_CLIENT_PERSISTENT;
|
4595 |
+
$uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
|
4596 |
+
}
|
4597 |
+
|
4598 |
+
$resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
|
4599 |
+
|
4600 |
+
if (!$resource) {
|
4601 |
+
$this->onConnectionError(trim($errstr), $errno);
|
4602 |
+
}
|
4603 |
+
|
4604 |
+
if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
|
4605 |
+
$rwtimeout = (float) $parameters->read_write_timeout;
|
4606 |
+
$rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
|
4607 |
+
|
4608 |
+
$timeout = array(
|
4609 |
+
'sec' => $timeoutSeconds = floor($rwtimeout),
|
4610 |
+
'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
|
4611 |
+
);
|
4612 |
+
|
4613 |
+
$socket = $socket ?: socket_import_stream($resource);
|
4614 |
+
@socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
|
4615 |
+
@socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
|
4616 |
+
}
|
4617 |
+
|
4618 |
+
if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
|
4619 |
+
$socket = $socket ?: socket_import_stream($resource);
|
4620 |
+
socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
|
4621 |
+
}
|
4622 |
+
|
4623 |
+
return $resource;
|
4624 |
+
}
|
4625 |
+
|
4626 |
+
/**
|
4627 |
+
* Creates a new instance of the protocol reader resource.
|
4628 |
+
*
|
4629 |
+
* @return resource
|
4630 |
+
*/
|
4631 |
+
private function createReader()
|
4632 |
+
{
|
4633 |
+
$reader = phpiredis_reader_create();
|
4634 |
+
|
4635 |
+
phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
|
4636 |
+
phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
|
4637 |
+
|
4638 |
+
return $reader;
|
4639 |
+
}
|
4640 |
+
|
4641 |
+
/**
|
4642 |
+
* Returns the underlying protocol reader resource.
|
4643 |
+
*
|
4644 |
+
* @return resource
|
4645 |
+
*/
|
4646 |
+
protected function getReader()
|
4647 |
+
{
|
4648 |
+
return $this->reader;
|
4649 |
+
}
|
4650 |
+
|
4651 |
+
/**
|
4652 |
+
* Returns the handler used by the protocol reader for inline responses.
|
4653 |
+
*
|
4654 |
+
* @return \Closure
|
4655 |
+
*/
|
4656 |
+
protected function getStatusHandler()
|
4657 |
+
{
|
4658 |
+
return function ($payload) {
|
4659 |
+
return StatusResponse::get($payload);
|
4660 |
+
};
|
4661 |
+
}
|
4662 |
+
|
4663 |
+
/**
|
4664 |
+
* Returns the handler used by the protocol reader for error responses.
|
4665 |
+
*
|
4666 |
+
* @return \Closure
|
4667 |
+
*/
|
4668 |
+
protected function getErrorHandler()
|
4669 |
+
{
|
4670 |
+
return function ($errorMessage) {
|
4671 |
+
return new ErrorResponse($errorMessage);
|
4672 |
+
};
|
4673 |
+
}
|
4674 |
+
|
4675 |
+
/**
|
4676 |
+
* {@inheritdoc}
|
4677 |
+
*/
|
4678 |
+
public function read()
|
4679 |
+
{
|
4680 |
+
$socket = $this->getResource();
|
4681 |
+
$reader = $this->reader;
|
4682 |
+
|
4683 |
+
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
|
4684 |
+
$buffer = stream_socket_recvfrom($socket, 4096);
|
4685 |
+
|
4686 |
+
if ($buffer === false || $buffer === '') {
|
4687 |
+
$this->onConnectionError('Error while reading bytes from the server.');
|
4688 |
+
}
|
4689 |
+
|
4690 |
+
phpiredis_reader_feed($reader, $buffer);
|
4691 |
+
}
|
4692 |
+
|
4693 |
+
if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
|
4694 |
+
return phpiredis_reader_get_reply($reader);
|
4695 |
+
} else {
|
4696 |
+
$this->onProtocolError(phpiredis_reader_get_error($reader));
|
4697 |
+
|
4698 |
+
return;
|
4699 |
+
}
|
4700 |
+
}
|
4701 |
+
|
4702 |
+
/**
|
4703 |
+
* {@inheritdoc}
|
4704 |
+
*/
|
4705 |
+
public function writeRequest(CommandInterface $command)
|
4706 |
+
{
|
4707 |
+
$arguments = $command->getArguments();
|
4708 |
+
array_unshift($arguments, $command->getId());
|
4709 |
+
|
4710 |
+
$this->write(phpiredis_format_command($arguments));
|
4711 |
+
}
|
4712 |
+
|
4713 |
+
/**
|
4714 |
+
* {@inheritdoc}
|
4715 |
+
*/
|
4716 |
+
public function __wakeup()
|
4717 |
+
{
|
4718 |
+
$this->assertExtensions();
|
4719 |
+
$this->reader = $this->createReader();
|
4720 |
+
}
|
4721 |
+
}
|
4722 |
+
|
4723 |
+
/**
|
4724 |
+
* This class implements a Predis connection that actually talks with Webdis
|
4725 |
+
* instead of connecting directly to Redis. It relies on the cURL extension to
|
4726 |
+
* communicate with the web server and the phpiredis extension to parse the
|
4727 |
+
* protocol for responses returned in the http response bodies.
|
4728 |
+
*
|
4729 |
+
* Some features are not yet available or they simply cannot be implemented:
|
4730 |
+
* - Pipelining commands.
|
4731 |
+
* - Publish / Subscribe.
|
4732 |
+
* - MULTI / EXEC transactions (not yet supported by Webdis).
|
4733 |
+
*
|
4734 |
+
* The connection parameters supported by this class are:
|
4735 |
+
*
|
4736 |
+
* - scheme: must be 'http'.
|
4737 |
+
* - host: hostname or IP address of the server.
|
4738 |
+
* - port: TCP port of the server.
|
4739 |
+
* - timeout: timeout to perform the connection.
|
4740 |
+
* - user: username for authentication.
|
4741 |
+
* - pass: password for authentication.
|
4742 |
+
*
|
4743 |
+
* @link http://webd.is
|
4744 |
+
* @link http://github.com/nicolasff/webdis
|
4745 |
+
* @link http://github.com/seppo0010/phpiredis
|
4746 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
4747 |
+
*/
|
4748 |
+
class WebdisConnection implements NodeConnectionInterface
|
4749 |
+
{
|
4750 |
+
private $parameters;
|
4751 |
+
private $resource;
|
4752 |
+
private $reader;
|
4753 |
+
|
4754 |
+
/**
|
4755 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
4756 |
+
*
|
4757 |
+
* @throws \InvalidArgumentException
|
4758 |
+
*/
|
4759 |
+
public function __construct(ParametersInterface $parameters)
|
4760 |
+
{
|
4761 |
+
$this->assertExtensions();
|
4762 |
+
|
4763 |
+
if ($parameters->scheme !== 'http') {
|
4764 |
+
throw new InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'.");
|
4765 |
+
}
|
4766 |
+
|
4767 |
+
$this->parameters = $parameters;
|
4768 |
+
|
4769 |
+
$this->resource = $this->createCurl();
|
4770 |
+
$this->reader = $this->createReader();
|
4771 |
+
}
|
4772 |
+
|
4773 |
+
/**
|
4774 |
+
* Frees the underlying cURL and protocol reader resources when the garbage
|
4775 |
+
* collector kicks in.
|
4776 |
+
*/
|
4777 |
+
public function __destruct()
|
4778 |
+
{
|
4779 |
+
curl_close($this->resource);
|
4780 |
+
phpiredis_reader_destroy($this->reader);
|
4781 |
+
}
|
4782 |
+
|
4783 |
+
/**
|
4784 |
+
* Helper method used to throw on unsupported methods.
|
4785 |
+
*
|
4786 |
+
* @param string $method Name of the unsupported method.
|
4787 |
+
*
|
4788 |
+
* @throws NotSupportedException
|
4789 |
+
*/
|
4790 |
+
private function throwNotSupportedException($method)
|
4791 |
+
{
|
4792 |
+
$class = __CLASS__;
|
4793 |
+
throw new NotSupportedException("The method $class::$method() is not supported.");
|
4794 |
+
}
|
4795 |
+
|
4796 |
+
/**
|
4797 |
+
* Checks if the cURL and phpiredis extensions are loaded in PHP.
|
4798 |
+
*/
|
4799 |
+
private function assertExtensions()
|
4800 |
+
{
|
4801 |
+
if (!extension_loaded('curl')) {
|
4802 |
+
throw new NotSupportedException(
|
4803 |
+
'The "curl" extension is required by this connection backend.'
|
4804 |
+
);
|
4805 |
+
}
|
4806 |
+
|
4807 |
+
if (!extension_loaded('phpiredis')) {
|
4808 |
+
throw new NotSupportedException(
|
4809 |
+
'The "phpiredis" extension is required by this connection backend.'
|
4810 |
+
);
|
4811 |
+
}
|
4812 |
+
}
|
4813 |
+
|
4814 |
+
/**
|
4815 |
+
* Initializes cURL.
|
4816 |
+
*
|
4817 |
+
* @return resource
|
4818 |
+
*/
|
4819 |
+
private function createCurl()
|
4820 |
+
{
|
4821 |
+
$parameters = $this->getParameters();
|
4822 |
+
|
4823 |
+
$options = array(
|
4824 |
+
CURLOPT_FAILONERROR => true,
|
4825 |
+
CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000,
|
4826 |
+
CURLOPT_URL => "{$parameters->scheme}://{$parameters->host}:{$parameters->port}",
|
4827 |
+
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
4828 |
+
CURLOPT_POST => true,
|
4829 |
+
CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
|
4830 |
+
);
|
4831 |
+
|
4832 |
+
if (isset($parameters->user, $parameters->pass)) {
|
4833 |
+
$options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
|
4834 |
+
}
|
4835 |
+
|
4836 |
+
curl_setopt_array($resource = curl_init(), $options);
|
4837 |
+
|
4838 |
+
return $resource;
|
4839 |
+
}
|
4840 |
+
|
4841 |
+
/**
|
4842 |
+
* Initializes the phpiredis protocol reader.
|
4843 |
+
*
|
4844 |
+
* @return resource
|
4845 |
+
*/
|
4846 |
+
private function createReader()
|
4847 |
+
{
|
4848 |
+
$reader = phpiredis_reader_create();
|
4849 |
+
|
4850 |
+
phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
|
4851 |
+
phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
|
4852 |
+
|
4853 |
+
return $reader;
|
4854 |
+
}
|
4855 |
+
|
4856 |
+
/**
|
4857 |
+
* Returns the handler used by the protocol reader for inline responses.
|
4858 |
+
*
|
4859 |
+
* @return \Closure
|
4860 |
+
*/
|
4861 |
+
protected function getStatusHandler()
|
4862 |
+
{
|
4863 |
+
return function ($payload) {
|
4864 |
+
return StatusResponse::get($payload);
|
4865 |
+
};
|
4866 |
+
}
|
4867 |
+
|
4868 |
+
/**
|
4869 |
+
* Returns the handler used by the protocol reader for error responses.
|
4870 |
+
*
|
4871 |
+
* @return \Closure
|
4872 |
+
*/
|
4873 |
+
protected function getErrorHandler()
|
4874 |
+
{
|
4875 |
+
return function ($payload) {
|
4876 |
+
return new ErrorResponse($payload);
|
4877 |
+
};
|
4878 |
+
}
|
4879 |
+
|
4880 |
+
/**
|
4881 |
+
* Feeds the phpredis reader resource with the data read from the network.
|
4882 |
+
*
|
4883 |
+
* @param resource $resource Reader resource.
|
4884 |
+
* @param string $buffer Buffer of data read from a connection.
|
4885 |
+
*
|
4886 |
+
* @return int
|
4887 |
+
*/
|
4888 |
+
protected function feedReader($resource, $buffer)
|
4889 |
+
{
|
4890 |
+
phpiredis_reader_feed($this->reader, $buffer);
|
4891 |
+
|
4892 |
+
return strlen($buffer);
|
4893 |
+
}
|
4894 |
+
|
4895 |
+
/**
|
4896 |
+
* {@inheritdoc}
|
4897 |
+
*/
|
4898 |
+
public function connect()
|
4899 |
+
{
|
4900 |
+
// NOOP
|
4901 |
+
}
|
4902 |
+
|
4903 |
+
/**
|
4904 |
+
* {@inheritdoc}
|
4905 |
+
*/
|
4906 |
+
public function disconnect()
|
4907 |
+
{
|
4908 |
+
// NOOP
|
4909 |
+
}
|
4910 |
+
|
4911 |
+
/**
|
4912 |
+
* {@inheritdoc}
|
4913 |
+
*/
|
4914 |
+
public function isConnected()
|
4915 |
+
{
|
4916 |
+
return true;
|
4917 |
+
}
|
4918 |
+
|
4919 |
+
/**
|
4920 |
+
* Checks if the specified command is supported by this connection class.
|
4921 |
+
*
|
4922 |
+
* @param CommandInterface $command Command instance.
|
4923 |
+
*
|
4924 |
+
* @return string
|
4925 |
+
*
|
4926 |
+
* @throws NotSupportedException
|
4927 |
+
*/
|
4928 |
+
protected function getCommandId(CommandInterface $command)
|
4929 |
+
{
|
4930 |
+
switch ($commandID = $command->getId()) {
|
4931 |
+
case 'AUTH':
|
4932 |
+
case 'SELECT':
|
4933 |
+
case 'MULTI':
|
4934 |
+
case 'EXEC':
|
4935 |
+
case 'WATCH':
|
4936 |
+
case 'UNWATCH':
|
4937 |
+
case 'DISCARD':
|
4938 |
+
case 'MONITOR':
|
4939 |
+
throw new NotSupportedException("Command '$commandID' is not allowed by Webdis.");
|
4940 |
+
|
4941 |
+
default:
|
4942 |
+
return $commandID;
|
4943 |
+
}
|
4944 |
+
}
|
4945 |
+
|
4946 |
+
/**
|
4947 |
+
* {@inheritdoc}
|
4948 |
+
*/
|
4949 |
+
public function writeRequest(CommandInterface $command)
|
4950 |
+
{
|
4951 |
+
$this->throwNotSupportedException(__FUNCTION__);
|
4952 |
+
}
|
4953 |
+
|
4954 |
+
/**
|
4955 |
+
* {@inheritdoc}
|
4956 |
+
*/
|
4957 |
+
public function readResponse(CommandInterface $command)
|
4958 |
+
{
|
4959 |
+
$this->throwNotSupportedException(__FUNCTION__);
|
4960 |
+
}
|
4961 |
+
|
4962 |
+
/**
|
4963 |
+
* {@inheritdoc}
|
4964 |
+
*/
|
4965 |
+
public function executeCommand(CommandInterface $command)
|
4966 |
+
{
|
4967 |
+
$resource = $this->resource;
|
4968 |
+
$commandId = $this->getCommandId($command);
|
4969 |
+
|
4970 |
+
if ($arguments = $command->getArguments()) {
|
4971 |
+
$arguments = implode('/', array_map('urlencode', $arguments));
|
4972 |
+
$serializedCommand = "$commandId/$arguments.raw";
|
4973 |
+
} else {
|
4974 |
+
$serializedCommand = "$commandId.raw";
|
4975 |
+
}
|
4976 |
+
|
4977 |
+
curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
|
4978 |
+
|
4979 |
+
if (curl_exec($resource) === false) {
|
4980 |
+
$error = curl_error($resource);
|
4981 |
+
$errno = curl_errno($resource);
|
4982 |
+
|
4983 |
+
throw new ConnectionException($this, trim($error), $errno);
|
4984 |
+
}
|
4985 |
+
|
4986 |
+
if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
|
4987 |
+
throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
|
4988 |
+
}
|
4989 |
+
|
4990 |
+
return phpiredis_reader_get_reply($this->reader);
|
4991 |
+
}
|
4992 |
+
|
4993 |
+
/**
|
4994 |
+
* {@inheritdoc}
|
4995 |
+
*/
|
4996 |
+
public function getResource()
|
4997 |
+
{
|
4998 |
+
return $this->resource;
|
4999 |
+
}
|
5000 |
+
|
5001 |
+
/**
|
5002 |
+
* {@inheritdoc}
|
5003 |
+
*/
|
5004 |
+
public function getParameters()
|
5005 |
+
{
|
5006 |
+
return $this->parameters;
|
5007 |
+
}
|
5008 |
+
|
5009 |
+
/**
|
5010 |
+
* {@inheritdoc}
|
5011 |
+
*/
|
5012 |
+
public function addConnectCommand(CommandInterface $command)
|
5013 |
+
{
|
5014 |
+
$this->throwNotSupportedException(__FUNCTION__);
|
5015 |
+
}
|
5016 |
+
|
5017 |
+
/**
|
5018 |
+
* {@inheritdoc}
|
5019 |
+
*/
|
5020 |
+
public function read()
|
5021 |
+
{
|
5022 |
+
$this->throwNotSupportedException(__FUNCTION__);
|
5023 |
+
}
|
5024 |
+
|
5025 |
+
/**
|
5026 |
+
* {@inheritdoc}
|
5027 |
+
*/
|
5028 |
+
public function __toString()
|
5029 |
+
{
|
5030 |
+
return "{$this->parameters->host}:{$this->parameters->port}";
|
5031 |
+
}
|
5032 |
+
|
5033 |
+
/**
|
5034 |
+
* {@inheritdoc}
|
5035 |
+
*/
|
5036 |
+
public function __sleep()
|
5037 |
+
{
|
5038 |
+
return array('parameters');
|
5039 |
+
}
|
5040 |
+
|
5041 |
+
/**
|
5042 |
+
* {@inheritdoc}
|
5043 |
+
*/
|
5044 |
+
public function __wakeup()
|
5045 |
+
{
|
5046 |
+
$this->assertExtensions();
|
5047 |
+
|
5048 |
+
$this->resource = $this->createCurl();
|
5049 |
+
$this->reader = $this->createReader();
|
5050 |
+
}
|
5051 |
+
}
|
5052 |
+
|
5053 |
+
/**
|
5054 |
+
* This class provides the implementation of a Predis connection that uses the
|
5055 |
+
* PHP socket extension for network communication and wraps the phpiredis C
|
5056 |
+
* extension (PHP bindings for hiredis) to parse the Redis protocol.
|
5057 |
+
*
|
5058 |
+
* This class is intended to provide an optional low-overhead alternative for
|
5059 |
+
* processing responses from Redis compared to the standard pure-PHP classes.
|
5060 |
+
* Differences in speed when dealing with short inline responses are practically
|
5061 |
+
* nonexistent, the actual speed boost is for big multibulk responses when this
|
5062 |
+
* protocol processor can parse and return responses very fast.
|
5063 |
+
*
|
5064 |
+
* For instructions on how to build and install the phpiredis extension, please
|
5065 |
+
* consult the repository of the project.
|
5066 |
+
*
|
5067 |
+
* The connection parameters supported by this class are:
|
5068 |
+
*
|
5069 |
+
* - scheme: it can be either 'tcp' or 'unix'.
|
5070 |
+
* - host: hostname or IP address of the server.
|
5071 |
+
* - port: TCP port of the server.
|
5072 |
+
* - path: path of a UNIX domain socket when scheme is 'unix'.
|
5073 |
+
* - timeout: timeout to perform the connection.
|
5074 |
+
* - read_write_timeout: timeout of read / write operations.
|
5075 |
+
*
|
5076 |
+
* @link http://github.com/nrk/phpiredis
|
5077 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5078 |
+
*/
|
5079 |
+
class PhpiredisSocketConnection extends AbstractConnection
|
5080 |
+
{
|
5081 |
+
private $reader;
|
5082 |
+
|
5083 |
+
/**
|
5084 |
+
* {@inheritdoc}
|
5085 |
+
*/
|
5086 |
+
public function __construct(ParametersInterface $parameters)
|
5087 |
+
{
|
5088 |
+
$this->assertExtensions();
|
5089 |
+
|
5090 |
+
parent::__construct($parameters);
|
5091 |
+
|
5092 |
+
$this->reader = $this->createReader();
|
5093 |
+
}
|
5094 |
+
|
5095 |
+
/**
|
5096 |
+
* Disconnects from the server and destroys the underlying resource and the
|
5097 |
+
* protocol reader resource when PHP's garbage collector kicks in.
|
5098 |
+
*/
|
5099 |
+
public function __destruct()
|
5100 |
+
{
|
5101 |
+
phpiredis_reader_destroy($this->reader);
|
5102 |
+
|
5103 |
+
parent::__destruct();
|
5104 |
+
}
|
5105 |
+
|
5106 |
+
/**
|
5107 |
+
* Checks if the socket and phpiredis extensions are loaded in PHP.
|
5108 |
+
*/
|
5109 |
+
protected function assertExtensions()
|
5110 |
+
{
|
5111 |
+
if (!extension_loaded('sockets')) {
|
5112 |
+
throw new NotSupportedException(
|
5113 |
+
'The "sockets" extension is required by this connection backend.'
|
5114 |
+
);
|
5115 |
+
}
|
5116 |
+
|
5117 |
+
if (!extension_loaded('phpiredis')) {
|
5118 |
+
throw new NotSupportedException(
|
5119 |
+
'The "phpiredis" extension is required by this connection backend.'
|
5120 |
+
);
|
5121 |
+
}
|
5122 |
+
}
|
5123 |
+
|
5124 |
+
/**
|
5125 |
+
* {@inheritdoc}
|
5126 |
+
*/
|
5127 |
+
protected function assertParameters(ParametersInterface $parameters)
|
5128 |
+
{
|
5129 |
+
if (isset($parameters->persistent)) {
|
5130 |
+
throw new NotSupportedException(
|
5131 |
+
"Persistent connections are not supported by this connection backend."
|
5132 |
+
);
|
5133 |
+
}
|
5134 |
+
|
5135 |
+
return parent::assertParameters($parameters);
|
5136 |
+
}
|
5137 |
+
|
5138 |
+
/**
|
5139 |
+
* Creates a new instance of the protocol reader resource.
|
5140 |
+
*
|
5141 |
+
* @return resource
|
5142 |
+
*/
|
5143 |
+
private function createReader()
|
5144 |
+
{
|
5145 |
+
$reader = phpiredis_reader_create();
|
5146 |
+
|
5147 |
+
phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
|
5148 |
+
phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
|
5149 |
+
|
5150 |
+
return $reader;
|
5151 |
+
}
|
5152 |
+
|
5153 |
+
/**
|
5154 |
+
* Returns the underlying protocol reader resource.
|
5155 |
+
*
|
5156 |
+
* @return resource
|
5157 |
+
*/
|
5158 |
+
protected function getReader()
|
5159 |
+
{
|
5160 |
+
return $this->reader;
|
5161 |
+
}
|
5162 |
+
|
5163 |
+
/**
|
5164 |
+
* Returns the handler used by the protocol reader for inline responses.
|
5165 |
+
*
|
5166 |
+
* @return \Closure
|
5167 |
+
*/
|
5168 |
+
private function getStatusHandler()
|
5169 |
+
{
|
5170 |
+
return function ($payload) {
|
5171 |
+
return StatusResponse::get($payload);
|
5172 |
+
};
|
5173 |
+
}
|
5174 |
+
|
5175 |
+
/**
|
5176 |
+
* Returns the handler used by the protocol reader for error responses.
|
5177 |
+
*
|
5178 |
+
* @return \Closure
|
5179 |
+
*/
|
5180 |
+
protected function getErrorHandler()
|
5181 |
+
{
|
5182 |
+
return function ($payload) {
|
5183 |
+
return new ErrorResponse($payload);
|
5184 |
+
};
|
5185 |
+
}
|
5186 |
+
|
5187 |
+
/**
|
5188 |
+
* Helper method used to throw exceptions on socket errors.
|
5189 |
+
*/
|
5190 |
+
private function emitSocketError()
|
5191 |
+
{
|
5192 |
+
$errno = socket_last_error();
|
5193 |
+
$errstr = socket_strerror($errno);
|
5194 |
+
|
5195 |
+
$this->disconnect();
|
5196 |
+
|
5197 |
+
$this->onConnectionError(trim($errstr), $errno);
|
5198 |
+
}
|
5199 |
+
|
5200 |
+
/**
|
5201 |
+
* {@inheritdoc}
|
5202 |
+
*/
|
5203 |
+
protected function createResource()
|
5204 |
+
{
|
5205 |
+
$isUnix = $this->parameters->scheme === 'unix';
|
5206 |
+
$domain = $isUnix ? AF_UNIX : AF_INET;
|
5207 |
+
$protocol = $isUnix ? 0 : SOL_TCP;
|
5208 |
+
|
5209 |
+
$socket = @call_user_func('socket_create', $domain, SOCK_STREAM, $protocol);
|
5210 |
+
|
5211 |
+
if (!is_resource($socket)) {
|
5212 |
+
$this->emitSocketError();
|
5213 |
+
}
|
5214 |
+
|
5215 |
+
$this->setSocketOptions($socket, $this->parameters);
|
5216 |
+
|
5217 |
+
return $socket;
|
5218 |
+
}
|
5219 |
+
|
5220 |
+
/**
|
5221 |
+
* Sets options on the socket resource from the connection parameters.
|
5222 |
+
*
|
5223 |
+
* @param resource $socket Socket resource.
|
5224 |
+
* @param ParametersInterface $parameters Parameters used to initialize the connection.
|
5225 |
+
*/
|
5226 |
+
private function setSocketOptions($socket, ParametersInterface $parameters)
|
5227 |
+
{
|
5228 |
+
if ($parameters->scheme !== 'tcp') {
|
5229 |
+
return;
|
5230 |
+
}
|
5231 |
+
|
5232 |
+
if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) {
|
5233 |
+
$this->emitSocketError();
|
5234 |
+
}
|
5235 |
+
|
5236 |
+
if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
|
5237 |
+
$this->emitSocketError();
|
5238 |
+
}
|
5239 |
+
|
5240 |
+
if (isset($parameters->read_write_timeout)) {
|
5241 |
+
$rwtimeout = (float) $parameters->read_write_timeout;
|
5242 |
+
$timeoutSec = floor($rwtimeout);
|
5243 |
+
$timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000;
|
5244 |
+
|
5245 |
+
$timeout = array(
|
5246 |
+
'sec' => $timeoutSec,
|
5247 |
+
'usec' => $timeoutUsec,
|
5248 |
+
);
|
5249 |
+
|
5250 |
+
if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) {
|
5251 |
+
$this->emitSocketError();
|
5252 |
+
}
|
5253 |
+
|
5254 |
+
if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) {
|
5255 |
+
$this->emitSocketError();
|
5256 |
+
}
|
5257 |
+
}
|
5258 |
+
}
|
5259 |
+
|
5260 |
+
/**
|
5261 |
+
* Gets the address from the connection parameters.
|
5262 |
+
*
|
5263 |
+
* @param ParametersInterface $parameters Parameters used to initialize the connection.
|
5264 |
+
*
|
5265 |
+
* @return string
|
5266 |
+
*/
|
5267 |
+
protected static function getAddress(ParametersInterface $parameters)
|
5268 |
+
{
|
5269 |
+
if ($parameters->scheme === 'unix') {
|
5270 |
+
return $parameters->path;
|
5271 |
+
}
|
5272 |
+
|
5273 |
+
$host = $parameters->host;
|
5274 |
+
|
5275 |
+
if (ip2long($host) === false) {
|
5276 |
+
if (false === $addresses = gethostbynamel($host)) {
|
5277 |
+
return false;
|
5278 |
+
}
|
5279 |
+
|
5280 |
+
return $addresses[array_rand($addresses)];
|
5281 |
+
}
|
5282 |
+
|
5283 |
+
return $host;
|
5284 |
+
}
|
5285 |
+
|
5286 |
+
/**
|
5287 |
+
* Opens the actual connection to the server with a timeout.
|
5288 |
+
*
|
5289 |
+
* @param ParametersInterface $parameters Parameters used to initialize the connection.
|
5290 |
+
*
|
5291 |
+
* @return string
|
5292 |
+
*/
|
5293 |
+
private function connectWithTimeout(ParametersInterface $parameters)
|
5294 |
+
{
|
5295 |
+
if (false === $host = self::getAddress($parameters)) {
|
5296 |
+
$this->onConnectionError("Cannot resolve the address of '$parameters->host'.");
|
5297 |
+
}
|
5298 |
+
|
5299 |
+
$socket = $this->getResource();
|
5300 |
+
|
5301 |
+
socket_set_nonblock($socket);
|
5302 |
+
|
5303 |
+
if (@socket_connect($socket, $host, (int) $parameters->port) === false) {
|
5304 |
+
$error = socket_last_error();
|
5305 |
+
|
5306 |
+
if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) {
|
5307 |
+
$this->emitSocketError();
|
5308 |
+
}
|
5309 |
+
}
|
5310 |
+
|
5311 |
+
socket_set_block($socket);
|
5312 |
+
|
5313 |
+
$null = null;
|
5314 |
+
$selectable = array($socket);
|
5315 |
+
|
5316 |
+
$timeout = (float) $parameters->timeout;
|
5317 |
+
$timeoutSecs = floor($timeout);
|
5318 |
+
$timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
|
5319 |
+
|
5320 |
+
$selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs);
|
5321 |
+
|
5322 |
+
if ($selected === 2) {
|
5323 |
+
$this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED);
|
5324 |
+
}
|
5325 |
+
if ($selected === 0) {
|
5326 |
+
$this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT);
|
5327 |
+
}
|
5328 |
+
if ($selected === false) {
|
5329 |
+
$this->emitSocketError();
|
5330 |
+
}
|
5331 |
+
}
|
5332 |
+
|
5333 |
+
/**
|
5334 |
+
* {@inheritdoc}
|
5335 |
+
*/
|
5336 |
+
public function connect()
|
5337 |
+
{
|
5338 |
+
if (parent::connect()) {
|
5339 |
+
$this->connectWithTimeout($this->parameters);
|
5340 |
+
|
5341 |
+
if ($this->initCommands) {
|
5342 |
+
foreach ($this->initCommands as $command) {
|
5343 |
+
$this->executeCommand($command);
|
5344 |
+
}
|
5345 |
+
}
|
5346 |
+
}
|
5347 |
+
}
|
5348 |
+
|
5349 |
+
/**
|
5350 |
+
* {@inheritdoc}
|
5351 |
+
*/
|
5352 |
+
public function disconnect()
|
5353 |
+
{
|
5354 |
+
if ($this->isConnected()) {
|
5355 |
+
socket_close($this->getResource());
|
5356 |
+
parent::disconnect();
|
5357 |
+
}
|
5358 |
+
}
|
5359 |
+
|
5360 |
+
/**
|
5361 |
+
* {@inheritdoc}
|
5362 |
+
*/
|
5363 |
+
protected function write($buffer)
|
5364 |
+
{
|
5365 |
+
$socket = $this->getResource();
|
5366 |
+
|
5367 |
+
while (($length = strlen($buffer)) > 0) {
|
5368 |
+
$written = socket_write($socket, $buffer, $length);
|
5369 |
+
|
5370 |
+
if ($length === $written) {
|
5371 |
+
return;
|
5372 |
+
}
|
5373 |
+
|
5374 |
+
if ($written === false) {
|
5375 |
+
$this->onConnectionError('Error while writing bytes to the server.');
|
5376 |
+
}
|
5377 |
+
|
5378 |
+
$buffer = substr($buffer, $written);
|
5379 |
+
}
|
5380 |
+
}
|
5381 |
+
|
5382 |
+
/**
|
5383 |
+
* {@inheritdoc}
|
5384 |
+
*/
|
5385 |
+
public function read()
|
5386 |
+
{
|
5387 |
+
$socket = $this->getResource();
|
5388 |
+
$reader = $this->reader;
|
5389 |
+
|
5390 |
+
while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
|
5391 |
+
if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '') {
|
5392 |
+
$this->emitSocketError();
|
5393 |
+
}
|
5394 |
+
|
5395 |
+
phpiredis_reader_feed($reader, $buffer);
|
5396 |
+
}
|
5397 |
+
|
5398 |
+
if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
|
5399 |
+
return phpiredis_reader_get_reply($reader);
|
5400 |
+
} else {
|
5401 |
+
$this->onProtocolError(phpiredis_reader_get_error($reader));
|
5402 |
+
|
5403 |
+
return;
|
5404 |
+
}
|
5405 |
+
}
|
5406 |
+
|
5407 |
+
/**
|
5408 |
+
* {@inheritdoc}
|
5409 |
+
*/
|
5410 |
+
public function writeRequest(CommandInterface $command)
|
5411 |
+
{
|
5412 |
+
$arguments = $command->getArguments();
|
5413 |
+
array_unshift($arguments, $command->getId());
|
5414 |
+
|
5415 |
+
$this->write(phpiredis_format_command($arguments));
|
5416 |
+
}
|
5417 |
+
|
5418 |
+
/**
|
5419 |
+
* {@inheritdoc}
|
5420 |
+
*/
|
5421 |
+
public function __wakeup()
|
5422 |
+
{
|
5423 |
+
$this->assertExtensions();
|
5424 |
+
$this->reader = $this->createReader();
|
5425 |
+
}
|
5426 |
+
}
|
5427 |
+
|
5428 |
+
/**
|
5429 |
+
* Connection abstraction to Redis servers based on PHP's stream that uses an
|
5430 |
+
* external protocol processor defining the protocol used for the communication.
|
5431 |
+
*
|
5432 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5433 |
+
*/
|
5434 |
+
class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface
|
5435 |
+
{
|
5436 |
+
protected $protocol;
|
5437 |
+
|
5438 |
+
/**
|
5439 |
+
* @param ParametersInterface $parameters Initialization parameters for the connection.
|
5440 |
+
* @param ProtocolProcessorInterface $protocol Protocol processor.
|
5441 |
+
*/
|
5442 |
+
public function __construct(
|
5443 |
+
ParametersInterface $parameters,
|
5444 |
+
ProtocolProcessorInterface $protocol = null
|
5445 |
+
) {
|
5446 |
+
$this->parameters = $this->assertParameters($parameters);
|
5447 |
+
$this->protocol = $protocol ?: new TextProtocolProcessor();
|
5448 |
+
}
|
5449 |
+
|
5450 |
+
/**
|
5451 |
+
* {@inheritdoc}
|
5452 |
+
*/
|
5453 |
+
public function getProtocol()
|
5454 |
+
{
|
5455 |
+
return $this->protocol;
|
5456 |
+
}
|
5457 |
+
|
5458 |
+
/**
|
5459 |
+
* {@inheritdoc}
|
5460 |
+
*/
|
5461 |
+
public function writeBuffer($buffer)
|
5462 |
+
{
|
5463 |
+
$this->write($buffer);
|
5464 |
+
}
|
5465 |
+
|
5466 |
+
/**
|
5467 |
+
* {@inheritdoc}
|
5468 |
+
*/
|
5469 |
+
public function readBuffer($length)
|
5470 |
+
{
|
5471 |
+
if ($length <= 0) {
|
5472 |
+
throw new InvalidArgumentException('Length parameter must be greater than 0.');
|
5473 |
+
}
|
5474 |
+
|
5475 |
+
$value = '';
|
5476 |
+
$socket = $this->getResource();
|
5477 |
+
|
5478 |
+
do {
|
5479 |
+
$chunk = fread($socket, $length);
|
5480 |
+
|
5481 |
+
if ($chunk === false || $chunk === '') {
|
5482 |
+
$this->onConnectionError('Error while reading bytes from the server.');
|
5483 |
+
}
|
5484 |
+
|
5485 |
+
$value .= $chunk;
|
5486 |
+
} while (($length -= strlen($chunk)) > 0);
|
5487 |
+
|
5488 |
+
return $value;
|
5489 |
+
}
|
5490 |
+
|
5491 |
+
/**
|
5492 |
+
* {@inheritdoc}
|
5493 |
+
*/
|
5494 |
+
public function readLine()
|
5495 |
+
{
|
5496 |
+
$value = '';
|
5497 |
+
$socket = $this->getResource();
|
5498 |
+
|
5499 |
+
do {
|
5500 |
+
$chunk = fgets($socket);
|
5501 |
+
|
5502 |
+
if ($chunk === false || $chunk === '') {
|
5503 |
+
$this->onConnectionError('Error while reading line from the server.');
|
5504 |
+
}
|
5505 |
+
|
5506 |
+
$value .= $chunk;
|
5507 |
+
} while (substr($value, -2) !== "\r\n");
|
5508 |
+
|
5509 |
+
return substr($value, 0, -2);
|
5510 |
+
}
|
5511 |
+
|
5512 |
+
/**
|
5513 |
+
* {@inheritdoc}
|
5514 |
+
*/
|
5515 |
+
public function writeRequest(CommandInterface $command)
|
5516 |
+
{
|
5517 |
+
$this->protocol->write($this, $command);
|
5518 |
+
}
|
5519 |
+
|
5520 |
+
/**
|
5521 |
+
* {@inheritdoc}
|
5522 |
+
*/
|
5523 |
+
public function read()
|
5524 |
+
{
|
5525 |
+
return $this->protocol->read($this);
|
5526 |
+
}
|
5527 |
+
|
5528 |
+
/**
|
5529 |
+
* {@inheritdoc}
|
5530 |
+
*/
|
5531 |
+
public function __sleep()
|
5532 |
+
{
|
5533 |
+
return array_merge(parent::__sleep(), array('protocol'));
|
5534 |
+
}
|
5535 |
+
}
|
5536 |
+
|
5537 |
+
/**
|
5538 |
+
* Exception class that identifies connection-related errors.
|
5539 |
+
*
|
5540 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5541 |
+
*/
|
5542 |
+
class ConnectionException extends CommunicationException
|
5543 |
+
{
|
5544 |
+
}
|
5545 |
+
|
5546 |
+
/**
|
5547 |
+
* Container for connection parameters used to initialize connections to Redis.
|
5548 |
+
*
|
5549 |
+
* {@inheritdoc}
|
5550 |
+
*
|
5551 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5552 |
+
*/
|
5553 |
+
class Parameters implements ParametersInterface
|
5554 |
+
{
|
5555 |
+
private $parameters;
|
5556 |
+
|
5557 |
+
private static $defaults = array(
|
5558 |
+
'scheme' => 'tcp',
|
5559 |
+
'host' => '127.0.0.1',
|
5560 |
+
'port' => 6379,
|
5561 |
+
'timeout' => 5.0,
|
5562 |
+
);
|
5563 |
+
|
5564 |
+
/**
|
5565 |
+
* @param array $parameters Named array of connection parameters.
|
5566 |
+
*/
|
5567 |
+
public function __construct(array $parameters = array())
|
5568 |
+
{
|
5569 |
+
$this->parameters = $this->filter($parameters) + $this->getDefaults();
|
5570 |
+
}
|
5571 |
+
|
5572 |
+
/**
|
5573 |
+
* Returns some default parameters with their values.
|
5574 |
+
*
|
5575 |
+
* @return array
|
5576 |
+
*/
|
5577 |
+
protected function getDefaults()
|
5578 |
+
{
|
5579 |
+
return self::$defaults;
|
5580 |
+
}
|
5581 |
+
|
5582 |
+
/**
|
5583 |
+
* Creates a new instance by supplying the initial parameters either in the
|
5584 |
+
* form of an URI string or a named array.
|
5585 |
+
*
|
5586 |
+
* @param array|string $parameters Set of connection parameters.
|
5587 |
+
*
|
5588 |
+
* @return Parameters
|
5589 |
+
*/
|
5590 |
+
public static function create($parameters)
|
5591 |
+
{
|
5592 |
+
if (is_string($parameters)) {
|
5593 |
+
$parameters = static::parse($parameters);
|
5594 |
+
}
|
5595 |
+
|
5596 |
+
return new static($parameters ?: array());
|
5597 |
+
}
|
5598 |
+
|
5599 |
+
/**
|
5600 |
+
* Parses an URI string returning an array of connection parameters.
|
5601 |
+
*
|
5602 |
+
* @param string $uri URI string.
|
5603 |
+
*
|
5604 |
+
* @return array
|
5605 |
+
*
|
5606 |
+
* @throws \InvalidArgumentException
|
5607 |
+
*/
|
5608 |
+
public static function parse($uri)
|
5609 |
+
{
|
5610 |
+
if (stripos($uri, 'unix') === 0) {
|
5611 |
+
// Hack to support URIs for UNIX sockets with minimal effort.
|
5612 |
+
$uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
|
5613 |
+
}
|
5614 |
+
|
5615 |
+
if (!($parsed = parse_url($uri)) || !isset($parsed['host'])) {
|
5616 |
+
throw new InvalidArgumentException("Invalid parameters URI: $uri");
|
5617 |
+
}
|
5618 |
+
|
5619 |
+
if (isset($parsed['query'])) {
|
5620 |
+
parse_str($parsed['query'], $queryarray);
|
5621 |
+
unset($parsed['query']);
|
5622 |
+
|
5623 |
+
$parsed = array_merge($parsed, $queryarray);
|
5624 |
+
}
|
5625 |
+
|
5626 |
+
return $parsed;
|
5627 |
+
}
|
5628 |
+
|
5629 |
+
/**
|
5630 |
+
* Validates and converts each value of the connection parameters array.
|
5631 |
+
*
|
5632 |
+
* @param array $parameters Connection parameters.
|
5633 |
+
*
|
5634 |
+
* @return array
|
5635 |
+
*/
|
5636 |
+
protected function filter(array $parameters)
|
5637 |
+
{
|
5638 |
+
return $parameters ?: array();
|
5639 |
+
}
|
5640 |
+
|
5641 |
+
/**
|
5642 |
+
* {@inheritdoc}
|
5643 |
+
*/
|
5644 |
+
public function __get($parameter)
|
5645 |
+
{
|
5646 |
+
if (isset($this->parameters[$parameter])) {
|
5647 |
+
return $this->parameters[$parameter];
|
5648 |
+
}
|
5649 |
+
}
|
5650 |
+
|
5651 |
+
/**
|
5652 |
+
* {@inheritdoc}
|
5653 |
+
*/
|
5654 |
+
public function __isset($parameter)
|
5655 |
+
{
|
5656 |
+
return isset($this->parameters[$parameter]);
|
5657 |
+
}
|
5658 |
+
|
5659 |
+
/**
|
5660 |
+
* {@inheritdoc}
|
5661 |
+
*/
|
5662 |
+
public function toArray()
|
5663 |
+
{
|
5664 |
+
return $this->parameters;
|
5665 |
+
}
|
5666 |
+
|
5667 |
+
/**
|
5668 |
+
* {@inheritdoc}
|
5669 |
+
*/
|
5670 |
+
public function __sleep()
|
5671 |
+
{
|
5672 |
+
return array('parameters');
|
5673 |
+
}
|
5674 |
+
}
|
5675 |
+
|
5676 |
+
/**
|
5677 |
+
* Standard connection factory for creating connections to Redis nodes.
|
5678 |
+
*
|
5679 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5680 |
+
*/
|
5681 |
+
class Factory implements FactoryInterface
|
5682 |
+
{
|
5683 |
+
protected $schemes = array(
|
5684 |
+
'tcp' => 'Predis\Connection\StreamConnection',
|
5685 |
+
'unix' => 'Predis\Connection\StreamConnection',
|
5686 |
+
'http' => 'Predis\Connection\WebdisConnection',
|
5687 |
+
);
|
5688 |
+
|
5689 |
+
/**
|
5690 |
+
* Checks if the provided argument represents a valid connection class
|
5691 |
+
* implementing Predis\Connection\NodeConnectionInterface. Optionally,
|
5692 |
+
* callable objects are used for lazy initialization of connection objects.
|
5693 |
+
*
|
5694 |
+
* @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
|
5695 |
+
*
|
5696 |
+
* @return mixed
|
5697 |
+
*
|
5698 |
+
* @throws \InvalidArgumentException
|
5699 |
+
*/
|
5700 |
+
protected function checkInitializer($initializer)
|
5701 |
+
{
|
5702 |
+
if (is_callable($initializer)) {
|
5703 |
+
return $initializer;
|
5704 |
+
}
|
5705 |
+
|
5706 |
+
$class = new ReflectionClass($initializer);
|
5707 |
+
|
5708 |
+
if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
|
5709 |
+
throw new InvalidArgumentException(
|
5710 |
+
'A connection initializer must be a valid connection class or a callable object.'
|
5711 |
+
);
|
5712 |
+
}
|
5713 |
+
|
5714 |
+
return $initializer;
|
5715 |
+
}
|
5716 |
+
|
5717 |
+
/**
|
5718 |
+
* {@inheritdoc}
|
5719 |
+
*/
|
5720 |
+
public function define($scheme, $initializer)
|
5721 |
+
{
|
5722 |
+
$this->schemes[$scheme] = $this->checkInitializer($initializer);
|
5723 |
+
}
|
5724 |
+
|
5725 |
+
/**
|
5726 |
+
* {@inheritdoc}
|
5727 |
+
*/
|
5728 |
+
public function undefine($scheme)
|
5729 |
+
{
|
5730 |
+
unset($this->schemes[$scheme]);
|
5731 |
+
}
|
5732 |
+
|
5733 |
+
/**
|
5734 |
+
* {@inheritdoc}
|
5735 |
+
*/
|
5736 |
+
public function create($parameters)
|
5737 |
+
{
|
5738 |
+
if (!$parameters instanceof ParametersInterface) {
|
5739 |
+
$parameters = $this->createParameters($parameters);
|
5740 |
+
}
|
5741 |
+
|
5742 |
+
$scheme = $parameters->scheme;
|
5743 |
+
|
5744 |
+
if (!isset($this->schemes[$scheme])) {
|
5745 |
+
throw new InvalidArgumentException("Unknown connection scheme: '$scheme'.");
|
5746 |
+
}
|
5747 |
+
|
5748 |
+
$initializer = $this->schemes[$scheme];
|
5749 |
+
|
5750 |
+
if (is_callable($initializer)) {
|
5751 |
+
$connection = call_user_func($initializer, $parameters, $this);
|
5752 |
+
} else {
|
5753 |
+
$connection = new $initializer($parameters);
|
5754 |
+
$this->prepareConnection($connection);
|
5755 |
+
}
|
5756 |
+
|
5757 |
+
if (!$connection instanceof NodeConnectionInterface) {
|
5758 |
+
throw new UnexpectedValueException(
|
5759 |
+
"Objects returned by connection initializers must implement ".
|
5760 |
+
"'Predis\Connection\NodeConnectionInterface'."
|
5761 |
+
);
|
5762 |
+
}
|
5763 |
+
|
5764 |
+
return $connection;
|
5765 |
+
}
|
5766 |
+
|
5767 |
+
/**
|
5768 |
+
* {@inheritdoc}
|
5769 |
+
*/
|
5770 |
+
public function aggregate(AggregateConnectionInterface $connection, array $parameters)
|
5771 |
+
{
|
5772 |
+
foreach ($parameters as $node) {
|
5773 |
+
$connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
|
5774 |
+
}
|
5775 |
+
}
|
5776 |
+
|
5777 |
+
/**
|
5778 |
+
* Creates a connection parameters instance from the supplied argument.
|
5779 |
+
*
|
5780 |
+
* @param mixed $parameters Original connection parameters.
|
5781 |
+
*
|
5782 |
+
* @return ParametersInterface
|
5783 |
+
*/
|
5784 |
+
protected function createParameters($parameters)
|
5785 |
+
{
|
5786 |
+
return Parameters::create($parameters);
|
5787 |
+
}
|
5788 |
+
|
5789 |
+
/**
|
5790 |
+
* Prepares a connection instance after its initialization.
|
5791 |
+
*
|
5792 |
+
* @param NodeConnectionInterface $connection Connection instance.
|
5793 |
+
*/
|
5794 |
+
protected function prepareConnection(NodeConnectionInterface $connection)
|
5795 |
+
{
|
5796 |
+
$parameters = $connection->getParameters();
|
5797 |
+
|
5798 |
+
if (isset($parameters->password)) {
|
5799 |
+
$connection->addConnectCommand(
|
5800 |
+
new RawCommand(array('AUTH', $parameters->password))
|
5801 |
+
);
|
5802 |
+
}
|
5803 |
+
|
5804 |
+
if (isset($parameters->database)) {
|
5805 |
+
$connection->addConnectCommand(
|
5806 |
+
new RawCommand(array('SELECT', $parameters->database))
|
5807 |
+
);
|
5808 |
+
}
|
5809 |
+
}
|
5810 |
+
}
|
5811 |
+
|
5812 |
+
/* --------------------------------------------------------------------------- */
|
5813 |
+
|
5814 |
+
namespace Predis\Profile;
|
5815 |
+
|
5816 |
+
use InvalidArgumentException;
|
5817 |
+
use ReflectionClass;
|
5818 |
+
use Predis\ClientException;
|
5819 |
+
use Predis\Command\CommandInterface;
|
5820 |
+
use Predis\Command\Processor\ProcessorInterface;
|
5821 |
+
|
5822 |
+
/**
|
5823 |
+
* A profile defines all the features and commands supported by certain versions
|
5824 |
+
* of Redis. Instances of Predis\Client should use a server profile matching the
|
5825 |
+
* version of Redis being used.
|
5826 |
+
*
|
5827 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5828 |
+
*/
|
5829 |
+
interface ProfileInterface
|
5830 |
+
{
|
5831 |
+
/**
|
5832 |
+
* Returns the profile version corresponding to the Redis version.
|
5833 |
+
*
|
5834 |
+
* @return string
|
5835 |
+
*/
|
5836 |
+
public function getVersion();
|
5837 |
+
|
5838 |
+
/**
|
5839 |
+
* Checks if the profile supports the specified command.
|
5840 |
+
*
|
5841 |
+
* @param string $commandID Command ID.
|
5842 |
+
*
|
5843 |
+
* @return bool
|
5844 |
+
*/
|
5845 |
+
public function supportsCommand($commandID);
|
5846 |
+
|
5847 |
+
/**
|
5848 |
+
* Checks if the profile supports the specified list of commands.
|
5849 |
+
*
|
5850 |
+
* @param array $commandIDs List of command IDs.
|
5851 |
+
*
|
5852 |
+
* @return string
|
5853 |
+
*/
|
5854 |
+
public function supportsCommands(array $commandIDs);
|
5855 |
+
|
5856 |
+
/**
|
5857 |
+
* Creates a new command instance.
|
5858 |
+
*
|
5859 |
+
* @param string $commandID Command ID.
|
5860 |
+
* @param array $arguments Arguments for the command.
|
5861 |
+
*
|
5862 |
+
* @return CommandInterface
|
5863 |
+
*/
|
5864 |
+
public function createCommand($commandID, array $arguments = array());
|
5865 |
+
}
|
5866 |
+
|
5867 |
+
/**
|
5868 |
+
* Base class implementing common functionalities for Redis server profiles.
|
5869 |
+
*
|
5870 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
5871 |
+
*/
|
5872 |
+
abstract class RedisProfile implements ProfileInterface
|
5873 |
+
{
|
5874 |
+
private $commands;
|
5875 |
+
private $processor;
|
5876 |
+
|
5877 |
+
/**
|
5878 |
+
*
|
5879 |
+
*/
|
5880 |
+
public function __construct()
|
5881 |
+
{
|
5882 |
+
$this->commands = $this->getSupportedCommands();
|
5883 |
+
}
|
5884 |
+
|
5885 |
+
/**
|
5886 |
+
* Returns a map of all the commands supported by the profile and their
|
5887 |
+
* actual PHP classes.
|
5888 |
+
*
|
5889 |
+
* @return array
|
5890 |
+
*/
|
5891 |
+
abstract protected function getSupportedCommands();
|
5892 |
+
|
5893 |
+
/**
|
5894 |
+
* {@inheritdoc}
|
5895 |
+
*/
|
5896 |
+
public function supportsCommand($commandID)
|
5897 |
+
{
|
5898 |
+
return isset($this->commands[strtoupper($commandID)]);
|
5899 |
+
}
|
5900 |
+
|
5901 |
+
/**
|
5902 |
+
* {@inheritdoc}
|
5903 |
+
*/
|
5904 |
+
public function supportsCommands(array $commandIDs)
|
5905 |
+
{
|
5906 |
+
foreach ($commandIDs as $commandID) {
|
5907 |
+
if (!$this->supportsCommand($commandID)) {
|
5908 |
+
return false;
|
5909 |
+
}
|
5910 |
+
}
|
5911 |
+
|
5912 |
+
return true;
|
5913 |
+
}
|
5914 |
+
|
5915 |
+
/**
|
5916 |
+
* Returns the fully-qualified name of a class representing the specified
|
5917 |
+
* command ID registered in the current server profile.
|
5918 |
+
*
|
5919 |
+
* @param string $commandID Command ID.
|
5920 |
+
*
|
5921 |
+
* @return string|null
|
5922 |
+
*/
|
5923 |
+
public function getCommandClass($commandID)
|
5924 |
+
{
|
5925 |
+
if (isset($this->commands[$commandID = strtoupper($commandID)])) {
|
5926 |
+
return $this->commands[$commandID];
|
5927 |
+
}
|
5928 |
+
}
|
5929 |
+
|
5930 |
+
/**
|
5931 |
+
* {@inheritdoc}
|
5932 |
+
*/
|
5933 |
+
public function createCommand($commandID, array $arguments = array())
|
5934 |
+
{
|
5935 |
+
$commandID = strtoupper($commandID);
|
5936 |
+
|
5937 |
+
if (!isset($this->commands[$commandID])) {
|
5938 |
+
throw new ClientException("Command '$commandID' is not a registered Redis command.");
|
5939 |
+
}
|
5940 |
+
|
5941 |
+
$commandClass = $this->commands[$commandID];
|
5942 |
+
$command = new $commandClass();
|
5943 |
+
$command->setArguments($arguments);
|
5944 |
+
|
5945 |
+
if (isset($this->processor)) {
|
5946 |
+
$this->processor->process($command);
|
5947 |
+
}
|
5948 |
+
|
5949 |
+
return $command;
|
5950 |
+
}
|
5951 |
+
|
5952 |
+
/**
|
5953 |
+
* Defines a new command in the server profile.
|
5954 |
+
*
|
5955 |
+
* @param string $commandID Command ID.
|
5956 |
+
* @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
|
5957 |
+
*
|
5958 |
+
* @throws \InvalidArgumentException
|
5959 |
+
*/
|
5960 |
+
public function defineCommand($commandID, $class)
|
5961 |
+
{
|
5962 |
+
$reflection = new ReflectionClass($class);
|
5963 |
+
|
5964 |
+
if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
|
5965 |
+
throw new InvalidArgumentException("The class '$class' is not a valid command class.");
|
5966 |
+
}
|
5967 |
+
|
5968 |
+
$this->commands[strtoupper($commandID)] = $class;
|
5969 |
+
}
|
5970 |
+
|
5971 |
+
/**
|
5972 |
+
* {@inheritdoc}
|
5973 |
+
*/
|
5974 |
+
public function setProcessor(ProcessorInterface $processor = null)
|
5975 |
+
{
|
5976 |
+
$this->processor = $processor;
|
5977 |
+
}
|
5978 |
+
|
5979 |
+
/**
|
5980 |
+
* {@inheritdoc}
|
5981 |
+
*/
|
5982 |
+
public function getProcessor()
|
5983 |
+
{
|
5984 |
+
return $this->processor;
|
5985 |
+
}
|
5986 |
+
|
5987 |
+
/**
|
5988 |
+
* Returns the version of server profile as its string representation.
|
5989 |
+
*
|
5990 |
+
* @return string
|
5991 |
+
*/
|
5992 |
+
public function __toString()
|
5993 |
+
{
|
5994 |
+
return $this->getVersion();
|
5995 |
+
}
|
5996 |
+
}
|
5997 |
+
|
5998 |
+
/**
|
5999 |
+
* Server profile for Redis 3.0.
|
6000 |
+
*
|
6001 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
6002 |
+
*/
|
6003 |
+
class RedisVersion300 extends RedisProfile
|
6004 |
+
{
|
6005 |
+
/**
|
6006 |
+
* {@inheritdoc}
|
6007 |
+
*/
|
6008 |
+
public function getVersion()
|
6009 |
+
{
|
6010 |
+
return '3.0';
|
6011 |
+
}
|
6012 |
+
|
6013 |
+
/**
|
6014 |
+
* {@inheritdoc}
|
6015 |
+
*/
|
6016 |
+
public function getSupportedCommands()
|
6017 |
+
{
|
6018 |
+
return array(
|
6019 |
+
/* ---------------- Redis 1.2 ---------------- */
|
6020 |
+
|
6021 |
+
/* commands operating on the key space */
|
6022 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
6023 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
6024 |
+
'TYPE' => 'Predis\Command\KeyType',
|
6025 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
6026 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
6027 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
6028 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
6029 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
6030 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
6031 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
6032 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
6033 |
+
'SORT' => 'Predis\Command\KeySort',
|
6034 |
+
'DUMP' => 'Predis\Command\KeyDump',
|
6035 |
+
'RESTORE' => 'Predis\Command\KeyRestore',
|
6036 |
+
|
6037 |
+
/* commands operating on string values */
|
6038 |
+
'SET' => 'Predis\Command\StringSet',
|
6039 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
6040 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
6041 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
6042 |
+
'GET' => 'Predis\Command\StringGet',
|
6043 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
6044 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
6045 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
6046 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
6047 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
6048 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
6049 |
+
|
6050 |
+
/* commands operating on lists */
|
6051 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
6052 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
6053 |
+
'LLEN' => 'Predis\Command\ListLength',
|
6054 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
6055 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
6056 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
6057 |
+
'LSET' => 'Predis\Command\ListSet',
|
6058 |
+
'LREM' => 'Predis\Command\ListRemove',
|
6059 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
6060 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
6061 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
6062 |
+
|
6063 |
+
/* commands operating on sets */
|
6064 |
+
'SADD' => 'Predis\Command\SetAdd',
|
6065 |
+
'SREM' => 'Predis\Command\SetRemove',
|
6066 |
+
'SPOP' => 'Predis\Command\SetPop',
|
6067 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
6068 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
6069 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
6070 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
6071 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
6072 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
6073 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
6074 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
6075 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
6076 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
6077 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
6078 |
+
|
6079 |
+
/* commands operating on sorted sets */
|
6080 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
6081 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
6082 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
6083 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
6084 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
6085 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
6086 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
6087 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
6088 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
6089 |
+
|
6090 |
+
/* connection related commands */
|
6091 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
6092 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
6093 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
6094 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
6095 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
6096 |
+
|
6097 |
+
/* remote server control commands */
|
6098 |
+
'INFO' => 'Predis\Command\ServerInfoV26x',
|
6099 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
6100 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
6101 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
6102 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
6103 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
6104 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
6105 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
6106 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
6107 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
6108 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
6109 |
+
|
6110 |
+
/* ---------------- Redis 2.0 ---------------- */
|
6111 |
+
|
6112 |
+
/* commands operating on string values */
|
6113 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
6114 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
6115 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
6116 |
+
|
6117 |
+
/* commands operating on lists */
|
6118 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
6119 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
6120 |
+
|
6121 |
+
/* commands operating on sorted sets */
|
6122 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
6123 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
6124 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
6125 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
6126 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
6127 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
6128 |
+
|
6129 |
+
/* commands operating on hashes */
|
6130 |
+
'HSET' => 'Predis\Command\HashSet',
|
6131 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
6132 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
6133 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
6134 |
+
'HGET' => 'Predis\Command\HashGet',
|
6135 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
6136 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
6137 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
6138 |
+
'HLEN' => 'Predis\Command\HashLength',
|
6139 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
6140 |
+
'HVALS' => 'Predis\Command\HashValues',
|
6141 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
6142 |
+
|
6143 |
+
/* transactions */
|
6144 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
6145 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
6146 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
6147 |
+
|
6148 |
+
/* publish - subscribe */
|
6149 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
6150 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
6151 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
6152 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
6153 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
6154 |
+
|
6155 |
+
/* remote server control commands */
|
6156 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
6157 |
+
|
6158 |
+
/* ---------------- Redis 2.2 ---------------- */
|
6159 |
+
|
6160 |
+
/* commands operating on the key space */
|
6161 |
+
'PERSIST' => 'Predis\Command\KeyPersist',
|
6162 |
+
|
6163 |
+
/* commands operating on string values */
|
6164 |
+
'STRLEN' => 'Predis\Command\StringStrlen',
|
6165 |
+
'SETRANGE' => 'Predis\Command\StringSetRange',
|
6166 |
+
'GETRANGE' => 'Predis\Command\StringGetRange',
|
6167 |
+
'SETBIT' => 'Predis\Command\StringSetBit',
|
6168 |
+
'GETBIT' => 'Predis\Command\StringGetBit',
|
6169 |
+
|
6170 |
+
/* commands operating on lists */
|
6171 |
+
'RPUSHX' => 'Predis\Command\ListPushTailX',
|
6172 |
+
'LPUSHX' => 'Predis\Command\ListPushHeadX',
|
6173 |
+
'LINSERT' => 'Predis\Command\ListInsert',
|
6174 |
+
'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
6175 |
+
|
6176 |
+
/* commands operating on sorted sets */
|
6177 |
+
'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
|
6178 |
+
|
6179 |
+
/* transactions */
|
6180 |
+
'WATCH' => 'Predis\Command\TransactionWatch',
|
6181 |
+
'UNWATCH' => 'Predis\Command\TransactionUnwatch',
|
6182 |
+
|
6183 |
+
/* remote server control commands */
|
6184 |
+
'OBJECT' => 'Predis\Command\ServerObject',
|
6185 |
+
'SLOWLOG' => 'Predis\Command\ServerSlowlog',
|
6186 |
+
|
6187 |
+
/* ---------------- Redis 2.4 ---------------- */
|
6188 |
+
|
6189 |
+
/* remote server control commands */
|
6190 |
+
'CLIENT' => 'Predis\Command\ServerClient',
|
6191 |
+
|
6192 |
+
/* ---------------- Redis 2.6 ---------------- */
|
6193 |
+
|
6194 |
+
/* commands operating on the key space */
|
6195 |
+
'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
|
6196 |
+
'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
|
6197 |
+
'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
|
6198 |
+
|
6199 |
+
/* commands operating on string values */
|
6200 |
+
'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
|
6201 |
+
'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
|
6202 |
+
'BITOP' => 'Predis\Command\StringBitOp',
|
6203 |
+
'BITCOUNT' => 'Predis\Command\StringBitCount',
|
6204 |
+
|
6205 |
+
/* commands operating on hashes */
|
6206 |
+
'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
|
6207 |
+
|
6208 |
+
/* scripting */
|
6209 |
+
'EVAL' => 'Predis\Command\ServerEval',
|
6210 |
+
'EVALSHA' => 'Predis\Command\ServerEvalSHA',
|
6211 |
+
'SCRIPT' => 'Predis\Command\ServerScript',
|
6212 |
+
|
6213 |
+
/* remote server control commands */
|
6214 |
+
'TIME' => 'Predis\Command\ServerTime',
|
6215 |
+
'SENTINEL' => 'Predis\Command\ServerSentinel',
|
6216 |
+
|
6217 |
+
/* ---------------- Redis 2.8 ---------------- */
|
6218 |
+
|
6219 |
+
/* commands operating on the key space */
|
6220 |
+
'SCAN' => 'Predis\Command\KeyScan',
|
6221 |
+
|
6222 |
+
/* commands operating on string values */
|
6223 |
+
'BITPOS' => 'Predis\Command\StringBitPos',
|
6224 |
+
|
6225 |
+
/* commands operating on sets */
|
6226 |
+
'SSCAN' => 'Predis\Command\SetScan',
|
6227 |
+
|
6228 |
+
/* commands operating on sorted sets */
|
6229 |
+
'ZSCAN' => 'Predis\Command\ZSetScan',
|
6230 |
+
'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
|
6231 |
+
'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
|
6232 |
+
'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
|
6233 |
+
|
6234 |
+
/* commands operating on hashes */
|
6235 |
+
'HSCAN' => 'Predis\Command\HashScan',
|
6236 |
+
|
6237 |
+
/* publish - subscribe */
|
6238 |
+
'PUBSUB' => 'Predis\Command\PubSubPubsub',
|
6239 |
+
|
6240 |
+
/* commands operating on HyperLogLog */
|
6241 |
+
'PFADD' => 'Predis\Command\HyperLogLogAdd',
|
6242 |
+
'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
|
6243 |
+
'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
|
6244 |
+
|
6245 |
+
/* remote server control commands */
|
6246 |
+
'COMMAND' => 'Predis\Command\ServerCommand',
|
6247 |
+
|
6248 |
+
/* ---------------- Redis 3.0 ---------------- */
|
6249 |
+
|
6250 |
+
);
|
6251 |
+
}
|
6252 |
+
}
|
6253 |
+
|
6254 |
+
/**
|
6255 |
+
* Server profile for Redis 2.6.
|
6256 |
+
*
|
6257 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
6258 |
+
*/
|
6259 |
+
class RedisVersion260 extends RedisProfile
|
6260 |
+
{
|
6261 |
+
/**
|
6262 |
+
* {@inheritdoc}
|
6263 |
+
*/
|
6264 |
+
public function getVersion()
|
6265 |
+
{
|
6266 |
+
return '2.6';
|
6267 |
+
}
|
6268 |
+
|
6269 |
+
/**
|
6270 |
+
* {@inheritdoc}
|
6271 |
+
*/
|
6272 |
+
public function getSupportedCommands()
|
6273 |
+
{
|
6274 |
+
return array(
|
6275 |
+
/* ---------------- Redis 1.2 ---------------- */
|
6276 |
+
|
6277 |
+
/* commands operating on the key space */
|
6278 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
6279 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
6280 |
+
'TYPE' => 'Predis\Command\KeyType',
|
6281 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
6282 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
6283 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
6284 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
6285 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
6286 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
6287 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
6288 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
6289 |
+
'SORT' => 'Predis\Command\KeySort',
|
6290 |
+
'DUMP' => 'Predis\Command\KeyDump',
|
6291 |
+
'RESTORE' => 'Predis\Command\KeyRestore',
|
6292 |
+
|
6293 |
+
/* commands operating on string values */
|
6294 |
+
'SET' => 'Predis\Command\StringSet',
|
6295 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
6296 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
6297 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
6298 |
+
'GET' => 'Predis\Command\StringGet',
|
6299 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
6300 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
6301 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
6302 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
6303 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
6304 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
6305 |
+
|
6306 |
+
/* commands operating on lists */
|
6307 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
6308 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
6309 |
+
'LLEN' => 'Predis\Command\ListLength',
|
6310 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
6311 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
6312 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
6313 |
+
'LSET' => 'Predis\Command\ListSet',
|
6314 |
+
'LREM' => 'Predis\Command\ListRemove',
|
6315 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
6316 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
6317 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
6318 |
+
|
6319 |
+
/* commands operating on sets */
|
6320 |
+
'SADD' => 'Predis\Command\SetAdd',
|
6321 |
+
'SREM' => 'Predis\Command\SetRemove',
|
6322 |
+
'SPOP' => 'Predis\Command\SetPop',
|
6323 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
6324 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
6325 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
6326 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
6327 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
6328 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
6329 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
6330 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
6331 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
6332 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
6333 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
6334 |
+
|
6335 |
+
/* commands operating on sorted sets */
|
6336 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
6337 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
6338 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
6339 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
6340 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
6341 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
6342 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
6343 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
6344 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
6345 |
+
|
6346 |
+
/* connection related commands */
|
6347 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
6348 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
6349 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
6350 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
6351 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
6352 |
+
|
6353 |
+
/* remote server control commands */
|
6354 |
+
'INFO' => 'Predis\Command\ServerInfoV26x',
|
6355 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
6356 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
6357 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
6358 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
6359 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
6360 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
6361 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
6362 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
6363 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
6364 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
6365 |
+
|
6366 |
+
/* ---------------- Redis 2.0 ---------------- */
|
6367 |
+
|
6368 |
+
/* commands operating on string values */
|
6369 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
6370 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
6371 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
6372 |
+
|
6373 |
+
/* commands operating on lists */
|
6374 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
6375 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
6376 |
+
|
6377 |
+
/* commands operating on sorted sets */
|
6378 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
6379 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
6380 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
6381 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
6382 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
6383 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
6384 |
+
|
6385 |
+
/* commands operating on hashes */
|
6386 |
+
'HSET' => 'Predis\Command\HashSet',
|
6387 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
6388 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
6389 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
6390 |
+
'HGET' => 'Predis\Command\HashGet',
|
6391 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
6392 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
6393 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
6394 |
+
'HLEN' => 'Predis\Command\HashLength',
|
6395 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
6396 |
+
'HVALS' => 'Predis\Command\HashValues',
|
6397 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
6398 |
+
|
6399 |
+
/* transactions */
|
6400 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
6401 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
6402 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
6403 |
+
|
6404 |
+
/* publish - subscribe */
|
6405 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
6406 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
6407 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
6408 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
6409 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
6410 |
+
|
6411 |
+
/* remote server control commands */
|
6412 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
6413 |
+
|
6414 |
+
/* ---------------- Redis 2.2 ---------------- */
|
6415 |
+
|
6416 |
+
/* commands operating on the key space */
|
6417 |
+
'PERSIST' => 'Predis\Command\KeyPersist',
|
6418 |
+
|
6419 |
+
/* commands operating on string values */
|
6420 |
+
'STRLEN' => 'Predis\Command\StringStrlen',
|
6421 |
+
'SETRANGE' => 'Predis\Command\StringSetRange',
|
6422 |
+
'GETRANGE' => 'Predis\Command\StringGetRange',
|
6423 |
+
'SETBIT' => 'Predis\Command\StringSetBit',
|
6424 |
+
'GETBIT' => 'Predis\Command\StringGetBit',
|
6425 |
+
|
6426 |
+
/* commands operating on lists */
|
6427 |
+
'RPUSHX' => 'Predis\Command\ListPushTailX',
|
6428 |
+
'LPUSHX' => 'Predis\Command\ListPushHeadX',
|
6429 |
+
'LINSERT' => 'Predis\Command\ListInsert',
|
6430 |
+
'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
6431 |
+
|
6432 |
+
/* commands operating on sorted sets */
|
6433 |
+
'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
|
6434 |
+
|
6435 |
+
/* transactions */
|
6436 |
+
'WATCH' => 'Predis\Command\TransactionWatch',
|
6437 |
+
'UNWATCH' => 'Predis\Command\TransactionUnwatch',
|
6438 |
+
|
6439 |
+
/* remote server control commands */
|
6440 |
+
'OBJECT' => 'Predis\Command\ServerObject',
|
6441 |
+
'SLOWLOG' => 'Predis\Command\ServerSlowlog',
|
6442 |
+
|
6443 |
+
/* ---------------- Redis 2.4 ---------------- */
|
6444 |
+
|
6445 |
+
/* remote server control commands */
|
6446 |
+
'CLIENT' => 'Predis\Command\ServerClient',
|
6447 |
+
|
6448 |
+
/* ---------------- Redis 2.6 ---------------- */
|
6449 |
+
|
6450 |
+
/* commands operating on the key space */
|
6451 |
+
'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
|
6452 |
+
'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
|
6453 |
+
'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
|
6454 |
+
|
6455 |
+
/* commands operating on string values */
|
6456 |
+
'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
|
6457 |
+
'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
|
6458 |
+
'BITOP' => 'Predis\Command\StringBitOp',
|
6459 |
+
'BITCOUNT' => 'Predis\Command\StringBitCount',
|
6460 |
+
|
6461 |
+
/* commands operating on hashes */
|
6462 |
+
'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
|
6463 |
+
|
6464 |
+
/* scripting */
|
6465 |
+
'EVAL' => 'Predis\Command\ServerEval',
|
6466 |
+
'EVALSHA' => 'Predis\Command\ServerEvalSHA',
|
6467 |
+
'SCRIPT' => 'Predis\Command\ServerScript',
|
6468 |
+
|
6469 |
+
/* remote server control commands */
|
6470 |
+
'TIME' => 'Predis\Command\ServerTime',
|
6471 |
+
'SENTINEL' => 'Predis\Command\ServerSentinel',
|
6472 |
+
);
|
6473 |
+
}
|
6474 |
+
}
|
6475 |
+
|
6476 |
+
/**
|
6477 |
+
* Server profile for Redis 2.8.
|
6478 |
+
*
|
6479 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
6480 |
+
*/
|
6481 |
+
class RedisVersion280 extends RedisProfile
|
6482 |
+
{
|
6483 |
+
/**
|
6484 |
+
* {@inheritdoc}
|
6485 |
+
*/
|
6486 |
+
public function getVersion()
|
6487 |
+
{
|
6488 |
+
return '2.8';
|
6489 |
+
}
|
6490 |
+
|
6491 |
+
/**
|
6492 |
+
* {@inheritdoc}
|
6493 |
+
*/
|
6494 |
+
public function getSupportedCommands()
|
6495 |
+
{
|
6496 |
+
return array(
|
6497 |
+
/* ---------------- Redis 1.2 ---------------- */
|
6498 |
+
|
6499 |
+
/* commands operating on the key space */
|
6500 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
6501 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
6502 |
+
'TYPE' => 'Predis\Command\KeyType',
|
6503 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
6504 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
6505 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
6506 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
6507 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
6508 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
6509 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
6510 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
6511 |
+
'SORT' => 'Predis\Command\KeySort',
|
6512 |
+
'DUMP' => 'Predis\Command\KeyDump',
|
6513 |
+
'RESTORE' => 'Predis\Command\KeyRestore',
|
6514 |
+
|
6515 |
+
/* commands operating on string values */
|
6516 |
+
'SET' => 'Predis\Command\StringSet',
|
6517 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
6518 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
6519 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
6520 |
+
'GET' => 'Predis\Command\StringGet',
|
6521 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
6522 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
6523 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
6524 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
6525 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
6526 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
6527 |
+
|
6528 |
+
/* commands operating on lists */
|
6529 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
6530 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
6531 |
+
'LLEN' => 'Predis\Command\ListLength',
|
6532 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
6533 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
6534 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
6535 |
+
'LSET' => 'Predis\Command\ListSet',
|
6536 |
+
'LREM' => 'Predis\Command\ListRemove',
|
6537 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
6538 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
6539 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
6540 |
+
|
6541 |
+
/* commands operating on sets */
|
6542 |
+
'SADD' => 'Predis\Command\SetAdd',
|
6543 |
+
'SREM' => 'Predis\Command\SetRemove',
|
6544 |
+
'SPOP' => 'Predis\Command\SetPop',
|
6545 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
6546 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
6547 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
6548 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
6549 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
6550 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
6551 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
6552 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
6553 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
6554 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
6555 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
6556 |
+
|
6557 |
+
/* commands operating on sorted sets */
|
6558 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
6559 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
6560 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
6561 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
6562 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
6563 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
6564 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
6565 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
6566 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
6567 |
+
|
6568 |
+
/* connection related commands */
|
6569 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
6570 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
6571 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
6572 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
6573 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
6574 |
+
|
6575 |
+
/* remote server control commands */
|
6576 |
+
'INFO' => 'Predis\Command\ServerInfoV26x',
|
6577 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
6578 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
6579 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
6580 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
6581 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
6582 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
6583 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
6584 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
6585 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
6586 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
6587 |
+
|
6588 |
+
/* ---------------- Redis 2.0 ---------------- */
|
6589 |
+
|
6590 |
+
/* commands operating on string values */
|
6591 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
6592 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
6593 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
6594 |
+
|
6595 |
+
/* commands operating on lists */
|
6596 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
6597 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
6598 |
+
|
6599 |
+
/* commands operating on sorted sets */
|
6600 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
6601 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
6602 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
6603 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
6604 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
6605 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
6606 |
+
|
6607 |
+
/* commands operating on hashes */
|
6608 |
+
'HSET' => 'Predis\Command\HashSet',
|
6609 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
6610 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
6611 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
6612 |
+
'HGET' => 'Predis\Command\HashGet',
|
6613 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
6614 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
6615 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
6616 |
+
'HLEN' => 'Predis\Command\HashLength',
|
6617 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
6618 |
+
'HVALS' => 'Predis\Command\HashValues',
|
6619 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
6620 |
+
|
6621 |
+
/* transactions */
|
6622 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
6623 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
6624 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
6625 |
+
|
6626 |
+
/* publish - subscribe */
|
6627 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
6628 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
6629 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
6630 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
6631 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
6632 |
+
|
6633 |
+
/* remote server control commands */
|
6634 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
6635 |
+
|
6636 |
+
/* ---------------- Redis 2.2 ---------------- */
|
6637 |
+
|
6638 |
+
/* commands operating on the key space */
|
6639 |
+
'PERSIST' => 'Predis\Command\KeyPersist',
|
6640 |
+
|
6641 |
+
/* commands operating on string values */
|
6642 |
+
'STRLEN' => 'Predis\Command\StringStrlen',
|
6643 |
+
'SETRANGE' => 'Predis\Command\StringSetRange',
|
6644 |
+
'GETRANGE' => 'Predis\Command\StringGetRange',
|
6645 |
+
'SETBIT' => 'Predis\Command\StringSetBit',
|
6646 |
+
'GETBIT' => 'Predis\Command\StringGetBit',
|
6647 |
+
|
6648 |
+
/* commands operating on lists */
|
6649 |
+
'RPUSHX' => 'Predis\Command\ListPushTailX',
|
6650 |
+
'LPUSHX' => 'Predis\Command\ListPushHeadX',
|
6651 |
+
'LINSERT' => 'Predis\Command\ListInsert',
|
6652 |
+
'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
6653 |
+
|
6654 |
+
/* commands operating on sorted sets */
|
6655 |
+
'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
|
6656 |
+
|
6657 |
+
/* transactions */
|
6658 |
+
'WATCH' => 'Predis\Command\TransactionWatch',
|
6659 |
+
'UNWATCH' => 'Predis\Command\TransactionUnwatch',
|
6660 |
+
|
6661 |
+
/* remote server control commands */
|
6662 |
+
'OBJECT' => 'Predis\Command\ServerObject',
|
6663 |
+
'SLOWLOG' => 'Predis\Command\ServerSlowlog',
|
6664 |
+
|
6665 |
+
/* ---------------- Redis 2.4 ---------------- */
|
6666 |
+
|
6667 |
+
/* remote server control commands */
|
6668 |
+
'CLIENT' => 'Predis\Command\ServerClient',
|
6669 |
+
|
6670 |
+
/* ---------------- Redis 2.6 ---------------- */
|
6671 |
+
|
6672 |
+
/* commands operating on the key space */
|
6673 |
+
'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
|
6674 |
+
'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
|
6675 |
+
'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
|
6676 |
+
|
6677 |
+
/* commands operating on string values */
|
6678 |
+
'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
|
6679 |
+
'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
|
6680 |
+
'BITOP' => 'Predis\Command\StringBitOp',
|
6681 |
+
'BITCOUNT' => 'Predis\Command\StringBitCount',
|
6682 |
+
|
6683 |
+
/* commands operating on hashes */
|
6684 |
+
'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
|
6685 |
+
|
6686 |
+
/* scripting */
|
6687 |
+
'EVAL' => 'Predis\Command\ServerEval',
|
6688 |
+
'EVALSHA' => 'Predis\Command\ServerEvalSHA',
|
6689 |
+
'SCRIPT' => 'Predis\Command\ServerScript',
|
6690 |
+
|
6691 |
+
/* remote server control commands */
|
6692 |
+
'TIME' => 'Predis\Command\ServerTime',
|
6693 |
+
'SENTINEL' => 'Predis\Command\ServerSentinel',
|
6694 |
+
|
6695 |
+
/* ---------------- Redis 2.8 ---------------- */
|
6696 |
+
|
6697 |
+
/* commands operating on the key space */
|
6698 |
+
'SCAN' => 'Predis\Command\KeyScan',
|
6699 |
+
|
6700 |
+
/* commands operating on string values */
|
6701 |
+
'BITPOS' => 'Predis\Command\StringBitPos',
|
6702 |
+
|
6703 |
+
/* commands operating on sets */
|
6704 |
+
'SSCAN' => 'Predis\Command\SetScan',
|
6705 |
+
|
6706 |
+
/* commands operating on sorted sets */
|
6707 |
+
'ZSCAN' => 'Predis\Command\ZSetScan',
|
6708 |
+
'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
|
6709 |
+
'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
|
6710 |
+
'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
|
6711 |
+
|
6712 |
+
/* commands operating on hashes */
|
6713 |
+
'HSCAN' => 'Predis\Command\HashScan',
|
6714 |
+
|
6715 |
+
/* publish - subscribe */
|
6716 |
+
'PUBSUB' => 'Predis\Command\PubSubPubsub',
|
6717 |
+
|
6718 |
+
/* commands operating on HyperLogLog */
|
6719 |
+
'PFADD' => 'Predis\Command\HyperLogLogAdd',
|
6720 |
+
'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
|
6721 |
+
'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
|
6722 |
+
|
6723 |
+
/* remote server control commands */
|
6724 |
+
'COMMAND' => 'Predis\Command\ServerCommand',
|
6725 |
+
);
|
6726 |
+
}
|
6727 |
+
}
|
6728 |
+
|
6729 |
+
/**
|
6730 |
+
* Server profile for Redis 2.4.
|
6731 |
+
*
|
6732 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
6733 |
+
*/
|
6734 |
+
class RedisVersion240 extends RedisProfile
|
6735 |
+
{
|
6736 |
+
/**
|
6737 |
+
* {@inheritdoc}
|
6738 |
+
*/
|
6739 |
+
public function getVersion()
|
6740 |
+
{
|
6741 |
+
return '2.4';
|
6742 |
+
}
|
6743 |
+
|
6744 |
+
/**
|
6745 |
+
* {@inheritdoc}
|
6746 |
+
*/
|
6747 |
+
public function getSupportedCommands()
|
6748 |
+
{
|
6749 |
+
return array(
|
6750 |
+
/* ---------------- Redis 1.2 ---------------- */
|
6751 |
+
|
6752 |
+
/* commands operating on the key space */
|
6753 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
6754 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
6755 |
+
'TYPE' => 'Predis\Command\KeyType',
|
6756 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
6757 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
6758 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
6759 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
6760 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
6761 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
6762 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
6763 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
6764 |
+
'SORT' => 'Predis\Command\KeySort',
|
6765 |
+
|
6766 |
+
/* commands operating on string values */
|
6767 |
+
'SET' => 'Predis\Command\StringSet',
|
6768 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
6769 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
6770 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
6771 |
+
'GET' => 'Predis\Command\StringGet',
|
6772 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
6773 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
6774 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
6775 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
6776 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
6777 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
6778 |
+
|
6779 |
+
/* commands operating on lists */
|
6780 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
6781 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
6782 |
+
'LLEN' => 'Predis\Command\ListLength',
|
6783 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
6784 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
6785 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
6786 |
+
'LSET' => 'Predis\Command\ListSet',
|
6787 |
+
'LREM' => 'Predis\Command\ListRemove',
|
6788 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
6789 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
6790 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
6791 |
+
|
6792 |
+
/* commands operating on sets */
|
6793 |
+
'SADD' => 'Predis\Command\SetAdd',
|
6794 |
+
'SREM' => 'Predis\Command\SetRemove',
|
6795 |
+
'SPOP' => 'Predis\Command\SetPop',
|
6796 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
6797 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
6798 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
6799 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
6800 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
6801 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
6802 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
6803 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
6804 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
6805 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
6806 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
6807 |
+
|
6808 |
+
/* commands operating on sorted sets */
|
6809 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
6810 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
6811 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
6812 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
6813 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
6814 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
6815 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
6816 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
6817 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
6818 |
+
|
6819 |
+
/* connection related commands */
|
6820 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
6821 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
6822 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
6823 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
6824 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
6825 |
+
|
6826 |
+
/* remote server control commands */
|
6827 |
+
'INFO' => 'Predis\Command\ServerInfo',
|
6828 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
6829 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
6830 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
6831 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
6832 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
6833 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
6834 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
6835 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
6836 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
6837 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
6838 |
+
|
6839 |
+
/* ---------------- Redis 2.0 ---------------- */
|
6840 |
+
|
6841 |
+
/* commands operating on string values */
|
6842 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
6843 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
6844 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
6845 |
+
|
6846 |
+
/* commands operating on lists */
|
6847 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
6848 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
6849 |
+
|
6850 |
+
/* commands operating on sorted sets */
|
6851 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
6852 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
6853 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
6854 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
6855 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
6856 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
6857 |
+
|
6858 |
+
/* commands operating on hashes */
|
6859 |
+
'HSET' => 'Predis\Command\HashSet',
|
6860 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
6861 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
6862 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
6863 |
+
'HGET' => 'Predis\Command\HashGet',
|
6864 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
6865 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
6866 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
6867 |
+
'HLEN' => 'Predis\Command\HashLength',
|
6868 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
6869 |
+
'HVALS' => 'Predis\Command\HashValues',
|
6870 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
6871 |
+
|
6872 |
+
/* transactions */
|
6873 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
6874 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
6875 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
6876 |
+
|
6877 |
+
/* publish - subscribe */
|
6878 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
6879 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
6880 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
6881 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
6882 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
6883 |
+
|
6884 |
+
/* remote server control commands */
|
6885 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
6886 |
+
|
6887 |
+
/* ---------------- Redis 2.2 ---------------- */
|
6888 |
+
|
6889 |
+
/* commands operating on the key space */
|
6890 |
+
'PERSIST' => 'Predis\Command\KeyPersist',
|
6891 |
+
|
6892 |
+
/* commands operating on string values */
|
6893 |
+
'STRLEN' => 'Predis\Command\StringStrlen',
|
6894 |
+
'SETRANGE' => 'Predis\Command\StringSetRange',
|
6895 |
+
'GETRANGE' => 'Predis\Command\StringGetRange',
|
6896 |
+
'SETBIT' => 'Predis\Command\StringSetBit',
|
6897 |
+
'GETBIT' => 'Predis\Command\StringGetBit',
|
6898 |
+
|
6899 |
+
/* commands operating on lists */
|
6900 |
+
'RPUSHX' => 'Predis\Command\ListPushTailX',
|
6901 |
+
'LPUSHX' => 'Predis\Command\ListPushHeadX',
|
6902 |
+
'LINSERT' => 'Predis\Command\ListInsert',
|
6903 |
+
'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
6904 |
+
|
6905 |
+
/* commands operating on sorted sets */
|
6906 |
+
'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
|
6907 |
+
|
6908 |
+
/* transactions */
|
6909 |
+
'WATCH' => 'Predis\Command\TransactionWatch',
|
6910 |
+
'UNWATCH' => 'Predis\Command\TransactionUnwatch',
|
6911 |
+
|
6912 |
+
/* remote server control commands */
|
6913 |
+
'OBJECT' => 'Predis\Command\ServerObject',
|
6914 |
+
'SLOWLOG' => 'Predis\Command\ServerSlowlog',
|
6915 |
+
|
6916 |
+
/* ---------------- Redis 2.4 ---------------- */
|
6917 |
+
|
6918 |
+
/* remote server control commands */
|
6919 |
+
'CLIENT' => 'Predis\Command\ServerClient',
|
6920 |
+
);
|
6921 |
+
}
|
6922 |
+
}
|
6923 |
+
|
6924 |
+
/**
|
6925 |
+
* Server profile for Redis 2.0.
|
6926 |
+
*
|
6927 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
6928 |
+
*/
|
6929 |
+
class RedisVersion200 extends RedisProfile
|
6930 |
+
{
|
6931 |
+
/**
|
6932 |
+
* {@inheritdoc}
|
6933 |
+
*/
|
6934 |
+
public function getVersion()
|
6935 |
+
{
|
6936 |
+
return '2.0';
|
6937 |
+
}
|
6938 |
+
|
6939 |
+
/**
|
6940 |
+
* {@inheritdoc}
|
6941 |
+
*/
|
6942 |
+
public function getSupportedCommands()
|
6943 |
+
{
|
6944 |
+
return array(
|
6945 |
+
/* ---------------- Redis 1.2 ---------------- */
|
6946 |
+
|
6947 |
+
/* commands operating on the key space */
|
6948 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
6949 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
6950 |
+
'TYPE' => 'Predis\Command\KeyType',
|
6951 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
6952 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
6953 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
6954 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
6955 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
6956 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
6957 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
6958 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
6959 |
+
'SORT' => 'Predis\Command\KeySort',
|
6960 |
+
|
6961 |
+
/* commands operating on string values */
|
6962 |
+
'SET' => 'Predis\Command\StringSet',
|
6963 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
6964 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
6965 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
6966 |
+
'GET' => 'Predis\Command\StringGet',
|
6967 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
6968 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
6969 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
6970 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
6971 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
6972 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
6973 |
+
|
6974 |
+
/* commands operating on lists */
|
6975 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
6976 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
6977 |
+
'LLEN' => 'Predis\Command\ListLength',
|
6978 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
6979 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
6980 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
6981 |
+
'LSET' => 'Predis\Command\ListSet',
|
6982 |
+
'LREM' => 'Predis\Command\ListRemove',
|
6983 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
6984 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
6985 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
6986 |
+
|
6987 |
+
/* commands operating on sets */
|
6988 |
+
'SADD' => 'Predis\Command\SetAdd',
|
6989 |
+
'SREM' => 'Predis\Command\SetRemove',
|
6990 |
+
'SPOP' => 'Predis\Command\SetPop',
|
6991 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
6992 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
6993 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
6994 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
6995 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
6996 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
6997 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
6998 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
6999 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
7000 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
7001 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
7002 |
+
|
7003 |
+
/* commands operating on sorted sets */
|
7004 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
7005 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
7006 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
7007 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
7008 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
7009 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
7010 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
7011 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
7012 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
7013 |
+
|
7014 |
+
/* connection related commands */
|
7015 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
7016 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
7017 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
7018 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
7019 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
7020 |
+
|
7021 |
+
/* remote server control commands */
|
7022 |
+
'INFO' => 'Predis\Command\ServerInfo',
|
7023 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
7024 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
7025 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
7026 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
7027 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
7028 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
7029 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
7030 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
7031 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
7032 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
7033 |
+
|
7034 |
+
/* ---------------- Redis 2.0 ---------------- */
|
7035 |
+
|
7036 |
+
/* commands operating on string values */
|
7037 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
7038 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
7039 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
7040 |
+
|
7041 |
+
/* commands operating on lists */
|
7042 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
7043 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
7044 |
+
|
7045 |
+
/* commands operating on sorted sets */
|
7046 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
7047 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
7048 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
7049 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
7050 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
7051 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
7052 |
+
|
7053 |
+
/* commands operating on hashes */
|
7054 |
+
'HSET' => 'Predis\Command\HashSet',
|
7055 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
7056 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
7057 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
7058 |
+
'HGET' => 'Predis\Command\HashGet',
|
7059 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
7060 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
7061 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
7062 |
+
'HLEN' => 'Predis\Command\HashLength',
|
7063 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
7064 |
+
'HVALS' => 'Predis\Command\HashValues',
|
7065 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
7066 |
+
|
7067 |
+
/* transactions */
|
7068 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
7069 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
7070 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
7071 |
+
|
7072 |
+
/* publish - subscribe */
|
7073 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
7074 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
7075 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
7076 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
7077 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
7078 |
+
|
7079 |
+
/* remote server control commands */
|
7080 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
7081 |
+
);
|
7082 |
+
}
|
7083 |
+
}
|
7084 |
+
|
7085 |
+
/**
|
7086 |
+
* Server profile for the current unstable version of Redis.
|
7087 |
+
*
|
7088 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7089 |
+
*/
|
7090 |
+
class RedisUnstable extends RedisVersion300
|
7091 |
+
{
|
7092 |
+
/**
|
7093 |
+
* {@inheritdoc}
|
7094 |
+
*/
|
7095 |
+
public function getVersion()
|
7096 |
+
{
|
7097 |
+
return '3.0';
|
7098 |
+
}
|
7099 |
+
|
7100 |
+
/**
|
7101 |
+
* {@inheritdoc}
|
7102 |
+
*/
|
7103 |
+
public function getSupportedCommands()
|
7104 |
+
{
|
7105 |
+
return array_merge(parent::getSupportedCommands(), array());
|
7106 |
+
}
|
7107 |
+
}
|
7108 |
+
|
7109 |
+
/**
|
7110 |
+
* Factory class for creating profile instances from strings.
|
7111 |
+
*
|
7112 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7113 |
+
*/
|
7114 |
+
final class Factory
|
7115 |
+
{
|
7116 |
+
private static $profiles = array(
|
7117 |
+
'2.0' => 'Predis\Profile\RedisVersion200',
|
7118 |
+
'2.2' => 'Predis\Profile\RedisVersion220',
|
7119 |
+
'2.4' => 'Predis\Profile\RedisVersion240',
|
7120 |
+
'2.6' => 'Predis\Profile\RedisVersion260',
|
7121 |
+
'2.8' => 'Predis\Profile\RedisVersion280',
|
7122 |
+
'3.0' => 'Predis\Profile\RedisVersion300',
|
7123 |
+
'default' => 'Predis\Profile\RedisVersion300',
|
7124 |
+
'dev' => 'Predis\Profile\RedisUnstable',
|
7125 |
+
);
|
7126 |
+
|
7127 |
+
/**
|
7128 |
+
*
|
7129 |
+
*/
|
7130 |
+
private function __construct()
|
7131 |
+
{
|
7132 |
+
// NOOP
|
7133 |
+
}
|
7134 |
+
|
7135 |
+
/**
|
7136 |
+
* Returns the default server profile.
|
7137 |
+
*
|
7138 |
+
* @return ProfileInterface
|
7139 |
+
*/
|
7140 |
+
public static function getDefault()
|
7141 |
+
{
|
7142 |
+
return self::get('default');
|
7143 |
+
}
|
7144 |
+
|
7145 |
+
/**
|
7146 |
+
* Returns the development server profile.
|
7147 |
+
*
|
7148 |
+
* @return ProfileInterface
|
7149 |
+
*/
|
7150 |
+
public static function getDevelopment()
|
7151 |
+
{
|
7152 |
+
return self::get('dev');
|
7153 |
+
}
|
7154 |
+
|
7155 |
+
/**
|
7156 |
+
* Registers a new server profile.
|
7157 |
+
*
|
7158 |
+
* @param string $alias Profile version or alias.
|
7159 |
+
* @param string $class FQN of a class implementing Predis\Profile\ProfileInterface.
|
7160 |
+
*
|
7161 |
+
* @throws \InvalidArgumentException
|
7162 |
+
*/
|
7163 |
+
public static function define($alias, $class)
|
7164 |
+
{
|
7165 |
+
$reflection = new ReflectionClass($class);
|
7166 |
+
|
7167 |
+
if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) {
|
7168 |
+
throw new InvalidArgumentException("The class '$class' is not a valid profile class.");
|
7169 |
+
}
|
7170 |
+
|
7171 |
+
self::$profiles[$alias] = $class;
|
7172 |
+
}
|
7173 |
+
|
7174 |
+
/**
|
7175 |
+
* Returns the specified server profile.
|
7176 |
+
*
|
7177 |
+
* @param string $version Profile version or alias.
|
7178 |
+
*
|
7179 |
+
* @return ProfileInterface
|
7180 |
+
*
|
7181 |
+
* @throws ClientException
|
7182 |
+
*/
|
7183 |
+
public static function get($version)
|
7184 |
+
{
|
7185 |
+
if (!isset(self::$profiles[$version])) {
|
7186 |
+
throw new ClientException("Unknown server profile: '$version'.");
|
7187 |
+
}
|
7188 |
+
|
7189 |
+
$profile = self::$profiles[$version];
|
7190 |
+
|
7191 |
+
return new $profile();
|
7192 |
+
}
|
7193 |
+
}
|
7194 |
+
|
7195 |
+
/**
|
7196 |
+
* Server profile for Redis 2.2.
|
7197 |
+
*
|
7198 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7199 |
+
*/
|
7200 |
+
class RedisVersion220 extends RedisProfile
|
7201 |
+
{
|
7202 |
+
/**
|
7203 |
+
* {@inheritdoc}
|
7204 |
+
*/
|
7205 |
+
public function getVersion()
|
7206 |
+
{
|
7207 |
+
return '2.2';
|
7208 |
+
}
|
7209 |
+
|
7210 |
+
/**
|
7211 |
+
* {@inheritdoc}
|
7212 |
+
*/
|
7213 |
+
public function getSupportedCommands()
|
7214 |
+
{
|
7215 |
+
return array(
|
7216 |
+
/* ---------------- Redis 1.2 ---------------- */
|
7217 |
+
|
7218 |
+
/* commands operating on the key space */
|
7219 |
+
'EXISTS' => 'Predis\Command\KeyExists',
|
7220 |
+
'DEL' => 'Predis\Command\KeyDelete',
|
7221 |
+
'TYPE' => 'Predis\Command\KeyType',
|
7222 |
+
'KEYS' => 'Predis\Command\KeyKeys',
|
7223 |
+
'RANDOMKEY' => 'Predis\Command\KeyRandom',
|
7224 |
+
'RENAME' => 'Predis\Command\KeyRename',
|
7225 |
+
'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
|
7226 |
+
'EXPIRE' => 'Predis\Command\KeyExpire',
|
7227 |
+
'EXPIREAT' => 'Predis\Command\KeyExpireAt',
|
7228 |
+
'TTL' => 'Predis\Command\KeyTimeToLive',
|
7229 |
+
'MOVE' => 'Predis\Command\KeyMove',
|
7230 |
+
'SORT' => 'Predis\Command\KeySort',
|
7231 |
+
|
7232 |
+
/* commands operating on string values */
|
7233 |
+
'SET' => 'Predis\Command\StringSet',
|
7234 |
+
'SETNX' => 'Predis\Command\StringSetPreserve',
|
7235 |
+
'MSET' => 'Predis\Command\StringSetMultiple',
|
7236 |
+
'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
|
7237 |
+
'GET' => 'Predis\Command\StringGet',
|
7238 |
+
'MGET' => 'Predis\Command\StringGetMultiple',
|
7239 |
+
'GETSET' => 'Predis\Command\StringGetSet',
|
7240 |
+
'INCR' => 'Predis\Command\StringIncrement',
|
7241 |
+
'INCRBY' => 'Predis\Command\StringIncrementBy',
|
7242 |
+
'DECR' => 'Predis\Command\StringDecrement',
|
7243 |
+
'DECRBY' => 'Predis\Command\StringDecrementBy',
|
7244 |
+
|
7245 |
+
/* commands operating on lists */
|
7246 |
+
'RPUSH' => 'Predis\Command\ListPushTail',
|
7247 |
+
'LPUSH' => 'Predis\Command\ListPushHead',
|
7248 |
+
'LLEN' => 'Predis\Command\ListLength',
|
7249 |
+
'LRANGE' => 'Predis\Command\ListRange',
|
7250 |
+
'LTRIM' => 'Predis\Command\ListTrim',
|
7251 |
+
'LINDEX' => 'Predis\Command\ListIndex',
|
7252 |
+
'LSET' => 'Predis\Command\ListSet',
|
7253 |
+
'LREM' => 'Predis\Command\ListRemove',
|
7254 |
+
'LPOP' => 'Predis\Command\ListPopFirst',
|
7255 |
+
'RPOP' => 'Predis\Command\ListPopLast',
|
7256 |
+
'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
|
7257 |
+
|
7258 |
+
/* commands operating on sets */
|
7259 |
+
'SADD' => 'Predis\Command\SetAdd',
|
7260 |
+
'SREM' => 'Predis\Command\SetRemove',
|
7261 |
+
'SPOP' => 'Predis\Command\SetPop',
|
7262 |
+
'SMOVE' => 'Predis\Command\SetMove',
|
7263 |
+
'SCARD' => 'Predis\Command\SetCardinality',
|
7264 |
+
'SISMEMBER' => 'Predis\Command\SetIsMember',
|
7265 |
+
'SINTER' => 'Predis\Command\SetIntersection',
|
7266 |
+
'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
|
7267 |
+
'SUNION' => 'Predis\Command\SetUnion',
|
7268 |
+
'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
|
7269 |
+
'SDIFF' => 'Predis\Command\SetDifference',
|
7270 |
+
'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
|
7271 |
+
'SMEMBERS' => 'Predis\Command\SetMembers',
|
7272 |
+
'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
|
7273 |
+
|
7274 |
+
/* commands operating on sorted sets */
|
7275 |
+
'ZADD' => 'Predis\Command\ZSetAdd',
|
7276 |
+
'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
|
7277 |
+
'ZREM' => 'Predis\Command\ZSetRemove',
|
7278 |
+
'ZRANGE' => 'Predis\Command\ZSetRange',
|
7279 |
+
'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
|
7280 |
+
'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
|
7281 |
+
'ZCARD' => 'Predis\Command\ZSetCardinality',
|
7282 |
+
'ZSCORE' => 'Predis\Command\ZSetScore',
|
7283 |
+
'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
|
7284 |
+
|
7285 |
+
/* connection related commands */
|
7286 |
+
'PING' => 'Predis\Command\ConnectionPing',
|
7287 |
+
'AUTH' => 'Predis\Command\ConnectionAuth',
|
7288 |
+
'SELECT' => 'Predis\Command\ConnectionSelect',
|
7289 |
+
'ECHO' => 'Predis\Command\ConnectionEcho',
|
7290 |
+
'QUIT' => 'Predis\Command\ConnectionQuit',
|
7291 |
+
|
7292 |
+
/* remote server control commands */
|
7293 |
+
'INFO' => 'Predis\Command\ServerInfo',
|
7294 |
+
'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
|
7295 |
+
'MONITOR' => 'Predis\Command\ServerMonitor',
|
7296 |
+
'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
|
7297 |
+
'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
|
7298 |
+
'FLUSHALL' => 'Predis\Command\ServerFlushAll',
|
7299 |
+
'SAVE' => 'Predis\Command\ServerSave',
|
7300 |
+
'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
|
7301 |
+
'LASTSAVE' => 'Predis\Command\ServerLastSave',
|
7302 |
+
'SHUTDOWN' => 'Predis\Command\ServerShutdown',
|
7303 |
+
'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
|
7304 |
+
|
7305 |
+
/* ---------------- Redis 2.0 ---------------- */
|
7306 |
+
|
7307 |
+
/* commands operating on string values */
|
7308 |
+
'SETEX' => 'Predis\Command\StringSetExpire',
|
7309 |
+
'APPEND' => 'Predis\Command\StringAppend',
|
7310 |
+
'SUBSTR' => 'Predis\Command\StringSubstr',
|
7311 |
+
|
7312 |
+
/* commands operating on lists */
|
7313 |
+
'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
|
7314 |
+
'BRPOP' => 'Predis\Command\ListPopLastBlocking',
|
7315 |
+
|
7316 |
+
/* commands operating on sorted sets */
|
7317 |
+
'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
|
7318 |
+
'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
|
7319 |
+
'ZCOUNT' => 'Predis\Command\ZSetCount',
|
7320 |
+
'ZRANK' => 'Predis\Command\ZSetRank',
|
7321 |
+
'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
|
7322 |
+
'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
|
7323 |
+
|
7324 |
+
/* commands operating on hashes */
|
7325 |
+
'HSET' => 'Predis\Command\HashSet',
|
7326 |
+
'HSETNX' => 'Predis\Command\HashSetPreserve',
|
7327 |
+
'HMSET' => 'Predis\Command\HashSetMultiple',
|
7328 |
+
'HINCRBY' => 'Predis\Command\HashIncrementBy',
|
7329 |
+
'HGET' => 'Predis\Command\HashGet',
|
7330 |
+
'HMGET' => 'Predis\Command\HashGetMultiple',
|
7331 |
+
'HDEL' => 'Predis\Command\HashDelete',
|
7332 |
+
'HEXISTS' => 'Predis\Command\HashExists',
|
7333 |
+
'HLEN' => 'Predis\Command\HashLength',
|
7334 |
+
'HKEYS' => 'Predis\Command\HashKeys',
|
7335 |
+
'HVALS' => 'Predis\Command\HashValues',
|
7336 |
+
'HGETALL' => 'Predis\Command\HashGetAll',
|
7337 |
+
|
7338 |
+
/* transactions */
|
7339 |
+
'MULTI' => 'Predis\Command\TransactionMulti',
|
7340 |
+
'EXEC' => 'Predis\Command\TransactionExec',
|
7341 |
+
'DISCARD' => 'Predis\Command\TransactionDiscard',
|
7342 |
+
|
7343 |
+
/* publish - subscribe */
|
7344 |
+
'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
|
7345 |
+
'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
|
7346 |
+
'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
|
7347 |
+
'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
|
7348 |
+
'PUBLISH' => 'Predis\Command\PubSubPublish',
|
7349 |
+
|
7350 |
+
/* remote server control commands */
|
7351 |
+
'CONFIG' => 'Predis\Command\ServerConfig',
|
7352 |
+
|
7353 |
+
/* ---------------- Redis 2.2 ---------------- */
|
7354 |
+
|
7355 |
+
/* commands operating on the key space */
|
7356 |
+
'PERSIST' => 'Predis\Command\KeyPersist',
|
7357 |
+
|
7358 |
+
/* commands operating on string values */
|
7359 |
+
'STRLEN' => 'Predis\Command\StringStrlen',
|
7360 |
+
'SETRANGE' => 'Predis\Command\StringSetRange',
|
7361 |
+
'GETRANGE' => 'Predis\Command\StringGetRange',
|
7362 |
+
'SETBIT' => 'Predis\Command\StringSetBit',
|
7363 |
+
'GETBIT' => 'Predis\Command\StringGetBit',
|
7364 |
+
|
7365 |
+
/* commands operating on lists */
|
7366 |
+
'RPUSHX' => 'Predis\Command\ListPushTailX',
|
7367 |
+
'LPUSHX' => 'Predis\Command\ListPushHeadX',
|
7368 |
+
'LINSERT' => 'Predis\Command\ListInsert',
|
7369 |
+
'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
|
7370 |
+
|
7371 |
+
/* commands operating on sorted sets */
|
7372 |
+
'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
|
7373 |
+
|
7374 |
+
/* transactions */
|
7375 |
+
'WATCH' => 'Predis\Command\TransactionWatch',
|
7376 |
+
'UNWATCH' => 'Predis\Command\TransactionUnwatch',
|
7377 |
+
|
7378 |
+
/* remote server control commands */
|
7379 |
+
'OBJECT' => 'Predis\Command\ServerObject',
|
7380 |
+
'SLOWLOG' => 'Predis\Command\ServerSlowlog',
|
7381 |
+
);
|
7382 |
+
}
|
7383 |
+
}
|
7384 |
+
|
7385 |
+
/* --------------------------------------------------------------------------- */
|
7386 |
+
|
7387 |
+
namespace Predis;
|
7388 |
+
|
7389 |
+
use InvalidArgumentException;
|
7390 |
+
use UnexpectedValueException;
|
7391 |
+
use Predis\Command\CommandInterface;
|
7392 |
+
use Predis\Command\RawCommand;
|
7393 |
+
use Predis\Command\ScriptCommand;
|
7394 |
+
use Predis\Configuration\Options;
|
7395 |
+
use Predis\Configuration\OptionsInterface;
|
7396 |
+
use Predis\Connection\ConnectionInterface;
|
7397 |
+
use Predis\Connection\AggregateConnectionInterface;
|
7398 |
+
use Predis\Connection\ParametersInterface;
|
7399 |
+
use Predis\Monitor\Consumer as MonitorConsumer;
|
7400 |
+
use Predis\Pipeline\Pipeline;
|
7401 |
+
use Predis\PubSub\Consumer as PubSubConsumer;
|
7402 |
+
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
7403 |
+
use Predis\Response\ResponseInterface;
|
7404 |
+
use Predis\Response\ServerException;
|
7405 |
+
use Predis\Transaction\MultiExec as MultiExecTransaction;
|
7406 |
+
use Predis\Profile\ProfileInterface;
|
7407 |
+
use Exception;
|
7408 |
+
use Predis\Connection\NodeConnectionInterface;
|
7409 |
+
|
7410 |
+
/**
|
7411 |
+
* Base exception class for Predis-related errors.
|
7412 |
+
*
|
7413 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7414 |
+
*/
|
7415 |
+
abstract class PredisException extends Exception
|
7416 |
+
{
|
7417 |
+
}
|
7418 |
+
|
7419 |
+
/**
|
7420 |
+
* Interface defining a client-side context such as a pipeline or transaction.
|
7421 |
+
*
|
7422 |
+
* @method $this del(array $keys)
|
7423 |
+
* @method $this dump($key)
|
7424 |
+
* @method $this exists($key)
|
7425 |
+
* @method $this expire($key, $seconds)
|
7426 |
+
* @method $this expireat($key, $timestamp)
|
7427 |
+
* @method $this keys($pattern)
|
7428 |
+
* @method $this move($key, $db)
|
7429 |
+
* @method $this object($subcommand, $key)
|
7430 |
+
* @method $this persist($key)
|
7431 |
+
* @method $this pexpire($key, $milliseconds)
|
7432 |
+
* @method $this pexpireat($key, $timestamp)
|
7433 |
+
* @method $this pttl($key)
|
7434 |
+
* @method $this randomkey()
|
7435 |
+
* @method $this rename($key, $target)
|
7436 |
+
* @method $this renamenx($key, $target)
|
7437 |
+
* @method $this scan($cursor, array $options = null)
|
7438 |
+
* @method $this sort($key, array $options = null)
|
7439 |
+
* @method $this ttl($key)
|
7440 |
+
* @method $this type($key)
|
7441 |
+
* @method $this append($key, $value)
|
7442 |
+
* @method $this bitcount($key, $start = null, $end = null)
|
7443 |
+
* @method $this bitop($operation, $destkey, $key)
|
7444 |
+
* @method $this decr($key)
|
7445 |
+
* @method $this decrby($key, $decrement)
|
7446 |
+
* @method $this get($key)
|
7447 |
+
* @method $this getbit($key, $offset)
|
7448 |
+
* @method $this getrange($key, $start, $end)
|
7449 |
+
* @method $this getset($key, $value)
|
7450 |
+
* @method $this incr($key)
|
7451 |
+
* @method $this incrby($key, $increment)
|
7452 |
+
* @method $this incrbyfloat($key, $increment)
|
7453 |
+
* @method $this mget(array $keys)
|
7454 |
+
* @method $this mset(array $dictionary)
|
7455 |
+
* @method $this msetnx(array $dictionary)
|
7456 |
+
* @method $this psetex($key, $milliseconds, $value)
|
7457 |
+
* @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
7458 |
+
* @method $this setbit($key, $offset, $value)
|
7459 |
+
* @method $this setex($key, $seconds, $value)
|
7460 |
+
* @method $this setnx($key, $value)
|
7461 |
+
* @method $this setrange($key, $offset, $value)
|
7462 |
+
* @method $this strlen($key)
|
7463 |
+
* @method $this hdel($key, array $fields)
|
7464 |
+
* @method $this hexists($key, $field)
|
7465 |
+
* @method $this hget($key, $field)
|
7466 |
+
* @method $this hgetall($key)
|
7467 |
+
* @method $this hincrby($key, $field, $increment)
|
7468 |
+
* @method $this hincrbyfloat($key, $field, $increment)
|
7469 |
+
* @method $this hkeys($key)
|
7470 |
+
* @method $this hlen($key)
|
7471 |
+
* @method $this hmget($key, array $fields)
|
7472 |
+
* @method $this hmset($key, array $dictionary)
|
7473 |
+
* @method $this hscan($key, $cursor, array $options = null)
|
7474 |
+
* @method $this hset($key, $field, $value)
|
7475 |
+
* @method $this hsetnx($key, $field, $value)
|
7476 |
+
* @method $this hvals($key)
|
7477 |
+
* @method $this blpop(array $keys, $timeout)
|
7478 |
+
* @method $this brpop(array $keys, $timeout)
|
7479 |
+
* @method $this brpoplpush($source, $destination, $timeout)
|
7480 |
+
* @method $this lindex($key, $index)
|
7481 |
+
* @method $this linsert($key, $whence, $pivot, $value)
|
7482 |
+
* @method $this llen($key)
|
7483 |
+
* @method $this lpop($key)
|
7484 |
+
* @method $this lpush($key, array $values)
|
7485 |
+
* @method $this lpushx($key, $value)
|
7486 |
+
* @method $this lrange($key, $start, $stop)
|
7487 |
+
* @method $this lrem($key, $count, $value)
|
7488 |
+
* @method $this lset($key, $index, $value)
|
7489 |
+
* @method $this ltrim($key, $start, $stop)
|
7490 |
+
* @method $this rpop($key)
|
7491 |
+
* @method $this rpoplpush($source, $destination)
|
7492 |
+
* @method $this rpush($key, array $values)
|
7493 |
+
* @method $this rpushx($key, $value)
|
7494 |
+
* @method $this sadd($key, array $members)
|
7495 |
+
* @method $this scard($key)
|
7496 |
+
* @method $this sdiff(array $keys)
|
7497 |
+
* @method $this sdiffstore($destination, array $keys)
|
7498 |
+
* @method $this sinter(array $keys)
|
7499 |
+
* @method $this sinterstore($destination, array $keys)
|
7500 |
+
* @method $this sismember($key, $member)
|
7501 |
+
* @method $this smembers($key)
|
7502 |
+
* @method $this smove($source, $destination, $member)
|
7503 |
+
* @method $this spop($key)
|
7504 |
+
* @method $this srandmember($key, $count = null)
|
7505 |
+
* @method $this srem($key, $member)
|
7506 |
+
* @method $this sscan($key, $cursor, array $options = null)
|
7507 |
+
* @method $this sunion(array $keys)
|
7508 |
+
* @method $this sunionstore($destination, array $keys)
|
7509 |
+
* @method $this zadd($key, array $membersAndScoresDictionary)
|
7510 |
+
* @method $this zcard($key)
|
7511 |
+
* @method $this zcount($key, $min, $max)
|
7512 |
+
* @method $this zincrby($key, $increment, $member)
|
7513 |
+
* @method $this zinterstore($destination, array $keys, array $options = null)
|
7514 |
+
* @method $this zrange($key, $start, $stop, array $options = null)
|
7515 |
+
* @method $this zrangebyscore($key, $min, $max, array $options = null)
|
7516 |
+
* @method $this zrank($key, $member)
|
7517 |
+
* @method $this zrem($key, $member)
|
7518 |
+
* @method $this zremrangebyrank($key, $start, $stop)
|
7519 |
+
* @method $this zremrangebyscore($key, $min, $max)
|
7520 |
+
* @method $this zrevrange($key, $start, $stop, array $options = null)
|
7521 |
+
* @method $this zrevrangebyscore($key, $min, $max, array $options = null)
|
7522 |
+
* @method $this zrevrank($key, $member)
|
7523 |
+
* @method $this zunionstore($destination, array $keys, array $options = null)
|
7524 |
+
* @method $this zscore($key, $member)
|
7525 |
+
* @method $this zscan($key, $cursor, array $options = null)
|
7526 |
+
* @method $this zrangebylex($key, $start, $stop, array $options = null)
|
7527 |
+
* @method $this zremrangebylex($key, $min, $max)
|
7528 |
+
* @method $this zlexcount($key, $min, $max)
|
7529 |
+
* @method $this pfadd($key, array $elements)
|
7530 |
+
* @method $this pfmerge($destinationKey, array $sourceKeys)
|
7531 |
+
* @method $this pfcount(array $keys)
|
7532 |
+
* @method $this pubsub($subcommand, $argument)
|
7533 |
+
* @method $this publish($channel, $message)
|
7534 |
+
* @method $this discard()
|
7535 |
+
* @method $this exec()
|
7536 |
+
* @method $this multi()
|
7537 |
+
* @method $this unwatch()
|
7538 |
+
* @method $this watch($key)
|
7539 |
+
* @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
7540 |
+
* @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
7541 |
+
* @method $this script($subcommand, $argument = null)
|
7542 |
+
* @method $this auth($password)
|
7543 |
+
* @method $this echo($message)
|
7544 |
+
* @method $this ping($message = null)
|
7545 |
+
* @method $this select($database)
|
7546 |
+
* @method $this bgrewriteaof()
|
7547 |
+
* @method $this bgsave()
|
7548 |
+
* @method $this client($subcommand, $argument = null)
|
7549 |
+
* @method $this config($subcommand, $argument = null)
|
7550 |
+
* @method $this dbsize()
|
7551 |
+
* @method $this flushall()
|
7552 |
+
* @method $this flushdb()
|
7553 |
+
* @method $this info($section = null)
|
7554 |
+
* @method $this lastsave()
|
7555 |
+
* @method $this save()
|
7556 |
+
* @method $this slaveof($host, $port)
|
7557 |
+
* @method $this slowlog($subcommand, $argument = null)
|
7558 |
+
* @method $this time()
|
7559 |
+
* @method $this command()
|
7560 |
+
*
|
7561 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7562 |
+
*/
|
7563 |
+
interface ClientContextInterface
|
7564 |
+
{
|
7565 |
+
|
7566 |
+
/**
|
7567 |
+
* Sends the specified command instance to Redis.
|
7568 |
+
*
|
7569 |
+
* @param CommandInterface $command Command instance.
|
7570 |
+
*
|
7571 |
+
* @return mixed
|
7572 |
+
*/
|
7573 |
+
public function executeCommand(CommandInterface $command);
|
7574 |
+
|
7575 |
+
/**
|
7576 |
+
* Sends the specified command with its arguments to Redis.
|
7577 |
+
*
|
7578 |
+
* @param string $method Command ID.
|
7579 |
+
* @param array $arguments Arguments for the command.
|
7580 |
+
*
|
7581 |
+
* @return mixed
|
7582 |
+
*/
|
7583 |
+
public function __call($method, $arguments);
|
7584 |
+
|
7585 |
+
/**
|
7586 |
+
* Starts the execution of the context.
|
7587 |
+
*
|
7588 |
+
* @param mixed $callable Optional callback for execution.
|
7589 |
+
*
|
7590 |
+
* @return array
|
7591 |
+
*/
|
7592 |
+
public function execute($callable = null);
|
7593 |
+
}
|
7594 |
+
|
7595 |
+
/**
|
7596 |
+
* Base exception class for network-related errors.
|
7597 |
+
*
|
7598 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7599 |
+
*/
|
7600 |
+
abstract class CommunicationException extends PredisException
|
7601 |
+
{
|
7602 |
+
private $connection;
|
7603 |
+
|
7604 |
+
/**
|
7605 |
+
* @param NodeConnectionInterface $connection Connection that generated the exception.
|
7606 |
+
* @param string $message Error message.
|
7607 |
+
* @param int $code Error code.
|
7608 |
+
* @param Exception $innerException Inner exception for wrapping the original error.
|
7609 |
+
*/
|
7610 |
+
public function __construct(
|
7611 |
+
NodeConnectionInterface $connection,
|
7612 |
+
$message = null,
|
7613 |
+
$code = null,
|
7614 |
+
Exception $innerException = null
|
7615 |
+
) {
|
7616 |
+
parent::__construct($message, $code, $innerException);
|
7617 |
+
$this->connection = $connection;
|
7618 |
+
}
|
7619 |
+
|
7620 |
+
/**
|
7621 |
+
* Gets the connection that generated the exception.
|
7622 |
+
*
|
7623 |
+
* @return NodeConnectionInterface
|
7624 |
+
*/
|
7625 |
+
public function getConnection()
|
7626 |
+
{
|
7627 |
+
return $this->connection;
|
7628 |
+
}
|
7629 |
+
|
7630 |
+
/**
|
7631 |
+
* Indicates if the receiver should reset the underlying connection.
|
7632 |
+
*
|
7633 |
+
* @return bool
|
7634 |
+
*/
|
7635 |
+
public function shouldResetConnection()
|
7636 |
+
{
|
7637 |
+
return true;
|
7638 |
+
}
|
7639 |
+
|
7640 |
+
/**
|
7641 |
+
* Helper method to handle exceptions generated by a connection object.
|
7642 |
+
*
|
7643 |
+
* @param CommunicationException $exception Exception.
|
7644 |
+
*
|
7645 |
+
* @throws CommunicationException
|
7646 |
+
*/
|
7647 |
+
public static function handle(CommunicationException $exception)
|
7648 |
+
{
|
7649 |
+
if ($exception->shouldResetConnection()) {
|
7650 |
+
$connection = $exception->getConnection();
|
7651 |
+
|
7652 |
+
if ($connection->isConnected()) {
|
7653 |
+
$connection->disconnect();
|
7654 |
+
}
|
7655 |
+
}
|
7656 |
+
|
7657 |
+
throw $exception;
|
7658 |
+
}
|
7659 |
+
}
|
7660 |
+
|
7661 |
+
/**
|
7662 |
+
* Interface defining a client able to execute commands against Redis.
|
7663 |
+
*
|
7664 |
+
* All the commands exposed by the client generally have the same signature as
|
7665 |
+
* described by the Redis documentation, but some of them offer an additional
|
7666 |
+
* and more friendly interface to ease programming which is described in the
|
7667 |
+
* following list of methods:
|
7668 |
+
*
|
7669 |
+
* @method int del(array $keys)
|
7670 |
+
* @method string dump($key)
|
7671 |
+
* @method int exists($key)
|
7672 |
+
* @method int expire($key, $seconds)
|
7673 |
+
* @method int expireat($key, $timestamp)
|
7674 |
+
* @method array keys($pattern)
|
7675 |
+
* @method int move($key, $db)
|
7676 |
+
* @method mixed object($subcommand, $key)
|
7677 |
+
* @method int persist($key)
|
7678 |
+
* @method int pexpire($key, $milliseconds)
|
7679 |
+
* @method int pexpireat($key, $timestamp)
|
7680 |
+
* @method int pttl($key)
|
7681 |
+
* @method string randomkey()
|
7682 |
+
* @method mixed rename($key, $target)
|
7683 |
+
* @method int renamenx($key, $target)
|
7684 |
+
* @method array scan($cursor, array $options = null)
|
7685 |
+
* @method array sort($key, array $options = null)
|
7686 |
+
* @method int ttl($key)
|
7687 |
+
* @method mixed type($key)
|
7688 |
+
* @method int append($key, $value)
|
7689 |
+
* @method int bitcount($key, $start = null, $end = null)
|
7690 |
+
* @method int bitop($operation, $destkey, $key)
|
7691 |
+
* @method int decr($key)
|
7692 |
+
* @method int decrby($key, $decrement)
|
7693 |
+
* @method string get($key)
|
7694 |
+
* @method int getbit($key, $offset)
|
7695 |
+
* @method string getrange($key, $start, $end)
|
7696 |
+
* @method string getset($key, $value)
|
7697 |
+
* @method int incr($key)
|
7698 |
+
* @method int incrby($key, $increment)
|
7699 |
+
* @method string incrbyfloat($key, $increment)
|
7700 |
+
* @method array mget(array $keys)
|
7701 |
+
* @method mixed mset(array $dictionary)
|
7702 |
+
* @method int msetnx(array $dictionary)
|
7703 |
+
* @method mixed psetex($key, $milliseconds, $value)
|
7704 |
+
* @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
|
7705 |
+
* @method int setbit($key, $offset, $value)
|
7706 |
+
* @method int setex($key, $seconds, $value)
|
7707 |
+
* @method int setnx($key, $value)
|
7708 |
+
* @method int setrange($key, $offset, $value)
|
7709 |
+
* @method int strlen($key)
|
7710 |
+
* @method int hdel($key, array $fields)
|
7711 |
+
* @method int hexists($key, $field)
|
7712 |
+
* @method string hget($key, $field)
|
7713 |
+
* @method array hgetall($key)
|
7714 |
+
* @method int hincrby($key, $field, $increment)
|
7715 |
+
* @method string hincrbyfloat($key, $field, $increment)
|
7716 |
+
* @method array hkeys($key)
|
7717 |
+
* @method int hlen($key)
|
7718 |
+
* @method array hmget($key, array $fields)
|
7719 |
+
* @method mixed hmset($key, array $dictionary)
|
7720 |
+
* @method array hscan($key, $cursor, array $options = null)
|
7721 |
+
* @method int hset($key, $field, $value)
|
7722 |
+
* @method int hsetnx($key, $field, $value)
|
7723 |
+
* @method array hvals($key)
|
7724 |
+
* @method array blpop(array $keys, $timeout)
|
7725 |
+
* @method array brpop(array $keys, $timeout)
|
7726 |
+
* @method array brpoplpush($source, $destination, $timeout)
|
7727 |
+
* @method string lindex($key, $index)
|
7728 |
+
* @method int linsert($key, $whence, $pivot, $value)
|
7729 |
+
* @method int llen($key)
|
7730 |
+
* @method string lpop($key)
|
7731 |
+
* @method int lpush($key, array $values)
|
7732 |
+
* @method int lpushx($key, $value)
|
7733 |
+
* @method array lrange($key, $start, $stop)
|
7734 |
+
* @method int lrem($key, $count, $value)
|
7735 |
+
* @method mixed lset($key, $index, $value)
|
7736 |
+
* @method mixed ltrim($key, $start, $stop)
|
7737 |
+
* @method string rpop($key)
|
7738 |
+
* @method string rpoplpush($source, $destination)
|
7739 |
+
* @method int rpush($key, array $values)
|
7740 |
+
* @method int rpushx($key, $value)
|
7741 |
+
* @method int sadd($key, array $members)
|
7742 |
+
* @method int scard($key)
|
7743 |
+
* @method array sdiff(array $keys)
|
7744 |
+
* @method int sdiffstore($destination, array $keys)
|
7745 |
+
* @method array sinter(array $keys)
|
7746 |
+
* @method int sinterstore($destination, array $keys)
|
7747 |
+
* @method int sismember($key, $member)
|
7748 |
+
* @method array smembers($key)
|
7749 |
+
* @method int smove($source, $destination, $member)
|
7750 |
+
* @method string spop($key)
|
7751 |
+
* @method string srandmember($key, $count = null)
|
7752 |
+
* @method int srem($key, $member)
|
7753 |
+
* @method array sscan($key, $cursor, array $options = null)
|
7754 |
+
* @method array sunion(array $keys)
|
7755 |
+
* @method int sunionstore($destination, array $keys)
|
7756 |
+
* @method int zadd($key, array $membersAndScoresDictionary)
|
7757 |
+
* @method int zcard($key)
|
7758 |
+
* @method string zcount($key, $min, $max)
|
7759 |
+
* @method string zincrby($key, $increment, $member)
|
7760 |
+
* @method int zinterstore($destination, array $keys, array $options = null)
|
7761 |
+
* @method array zrange($key, $start, $stop, array $options = null)
|
7762 |
+
* @method array zrangebyscore($key, $min, $max, array $options = null)
|
7763 |
+
* @method int zrank($key, $member)
|
7764 |
+
* @method int zrem($key, $member)
|
7765 |
+
* @method int zremrangebyrank($key, $start, $stop)
|
7766 |
+
* @method int zremrangebyscore($key, $min, $max)
|
7767 |
+
* @method array zrevrange($key, $start, $stop, array $options = null)
|
7768 |
+
* @method array zrevrangebyscore($key, $min, $max, array $options = null)
|
7769 |
+
* @method int zrevrank($key, $member)
|
7770 |
+
* @method int zunionstore($destination, array $keys, array $options = null)
|
7771 |
+
* @method string zscore($key, $member)
|
7772 |
+
* @method array zscan($key, $cursor, array $options = null)
|
7773 |
+
* @method array zrangebylex($key, $start, $stop, array $options = null)
|
7774 |
+
* @method int zremrangebylex($key, $min, $max)
|
7775 |
+
* @method int zlexcount($key, $min, $max)
|
7776 |
+
* @method int pfadd($key, array $elements)
|
7777 |
+
* @method mixed pfmerge($destinationKey, array $sourceKeys)
|
7778 |
+
* @method int pfcount(array $keys)
|
7779 |
+
* @method mixed pubsub($subcommand, $argument)
|
7780 |
+
* @method int publish($channel, $message)
|
7781 |
+
* @method mixed discard()
|
7782 |
+
* @method array exec()
|
7783 |
+
* @method mixed multi()
|
7784 |
+
* @method mixed unwatch()
|
7785 |
+
* @method mixed watch($key)
|
7786 |
+
* @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
7787 |
+
* @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
|
7788 |
+
* @method mixed script($subcommand, $argument = null)
|
7789 |
+
* @method mixed auth($password)
|
7790 |
+
* @method string echo($message)
|
7791 |
+
* @method mixed ping($message = null)
|
7792 |
+
* @method mixed select($database)
|
7793 |
+
* @method mixed bgrewriteaof()
|
7794 |
+
* @method mixed bgsave()
|
7795 |
+
* @method mixed client($subcommand, $argument = null)
|
7796 |
+
* @method mixed config($subcommand, $argument = null)
|
7797 |
+
* @method int dbsize()
|
7798 |
+
* @method mixed flushall()
|
7799 |
+
* @method mixed flushdb()
|
7800 |
+
* @method array info($section = null)
|
7801 |
+
* @method int lastsave()
|
7802 |
+
* @method mixed save()
|
7803 |
+
* @method mixed slaveof($host, $port)
|
7804 |
+
* @method mixed slowlog($subcommand, $argument = null)
|
7805 |
+
* @method array time()
|
7806 |
+
* @method array command()
|
7807 |
+
*
|
7808 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7809 |
+
*/
|
7810 |
+
interface ClientInterface
|
7811 |
+
{
|
7812 |
+
/**
|
7813 |
+
* Returns the server profile used by the client.
|
7814 |
+
*
|
7815 |
+
* @return ProfileInterface
|
7816 |
+
*/
|
7817 |
+
public function getProfile();
|
7818 |
+
|
7819 |
+
/**
|
7820 |
+
* Returns the client options specified upon initialization.
|
7821 |
+
*
|
7822 |
+
* @return OptionsInterface
|
7823 |
+
*/
|
7824 |
+
public function getOptions();
|
7825 |
+
|
7826 |
+
/**
|
7827 |
+
* Opens the underlying connection to the server.
|
7828 |
+
*/
|
7829 |
+
public function connect();
|
7830 |
+
|
7831 |
+
/**
|
7832 |
+
* Closes the underlying connection from the server.
|
7833 |
+
*/
|
7834 |
+
public function disconnect();
|
7835 |
+
|
7836 |
+
/**
|
7837 |
+
* Returns the underlying connection instance.
|
7838 |
+
*
|
7839 |
+
* @return ConnectionInterface
|
7840 |
+
*/
|
7841 |
+
public function getConnection();
|
7842 |
+
|
7843 |
+
/**
|
7844 |
+
* Creates a new instance of the specified Redis command.
|
7845 |
+
*
|
7846 |
+
* @param string $method Command ID.
|
7847 |
+
* @param array $arguments Arguments for the command.
|
7848 |
+
*
|
7849 |
+
* @return CommandInterface
|
7850 |
+
*/
|
7851 |
+
public function createCommand($method, $arguments = array());
|
7852 |
+
|
7853 |
+
/**
|
7854 |
+
* Executes the specified Redis command.
|
7855 |
+
*
|
7856 |
+
* @param CommandInterface $command Command instance.
|
7857 |
+
*
|
7858 |
+
* @return mixed
|
7859 |
+
*/
|
7860 |
+
public function executeCommand(CommandInterface $command);
|
7861 |
+
|
7862 |
+
/**
|
7863 |
+
* Creates a Redis command with the specified arguments and sends a request
|
7864 |
+
* to the server.
|
7865 |
+
*
|
7866 |
+
* @param string $method Command ID.
|
7867 |
+
* @param array $arguments Arguments for the command.
|
7868 |
+
*
|
7869 |
+
* @return mixed
|
7870 |
+
*/
|
7871 |
+
public function __call($method, $arguments);
|
7872 |
+
}
|
7873 |
+
|
7874 |
+
/**
|
7875 |
+
* Exception class thrown when trying to use features not supported by certain
|
7876 |
+
* classes or abstractions of Predis.
|
7877 |
+
*
|
7878 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7879 |
+
*/
|
7880 |
+
class NotSupportedException extends PredisException
|
7881 |
+
{
|
7882 |
+
}
|
7883 |
+
|
7884 |
+
/**
|
7885 |
+
* Exception class that identifies client-side errors.
|
7886 |
+
*
|
7887 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7888 |
+
*/
|
7889 |
+
class ClientException extends PredisException
|
7890 |
+
{
|
7891 |
+
}
|
7892 |
+
|
7893 |
+
/**
|
7894 |
+
* Client class used for connecting and executing commands on Redis.
|
7895 |
+
*
|
7896 |
+
* This is the main high-level abstraction of Predis upon which various other
|
7897 |
+
* abstractions are built. Internally it aggregates various other classes each
|
7898 |
+
* one with its own responsibility and scope.
|
7899 |
+
*
|
7900 |
+
* {@inheritdoc}
|
7901 |
+
*
|
7902 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
7903 |
+
*/
|
7904 |
+
class Client implements ClientInterface
|
7905 |
+
{
|
7906 |
+
const VERSION = '1.0.1';
|
7907 |
+
|
7908 |
+
protected $connection;
|
7909 |
+
protected $options;
|
7910 |
+
private $profile;
|
7911 |
+
|
7912 |
+
/**
|
7913 |
+
* @param mixed $parameters Connection parameters for one or more servers.
|
7914 |
+
* @param mixed $options Options to configure some behaviours of the client.
|
7915 |
+
*/
|
7916 |
+
public function __construct($parameters = null, $options = null)
|
7917 |
+
{
|
7918 |
+
$this->options = $this->createOptions($options ?: array());
|
7919 |
+
$this->connection = $this->createConnection($parameters ?: array());
|
7920 |
+
$this->profile = $this->options->profile;
|
7921 |
+
}
|
7922 |
+
|
7923 |
+
/**
|
7924 |
+
* Creates a new instance of Predis\Configuration\Options from different
|
7925 |
+
* types of arguments or simply returns the passed argument if it is an
|
7926 |
+
* instance of Predis\Configuration\OptionsInterface.
|
7927 |
+
*
|
7928 |
+
* @param mixed $options Client options.
|
7929 |
+
*
|
7930 |
+
* @return OptionsInterface
|
7931 |
+
*
|
7932 |
+
* @throws \InvalidArgumentException
|
7933 |
+
*/
|
7934 |
+
protected function createOptions($options)
|
7935 |
+
{
|
7936 |
+
if (is_array($options)) {
|
7937 |
+
return new Options($options);
|
7938 |
+
}
|
7939 |
+
|
7940 |
+
if ($options instanceof OptionsInterface) {
|
7941 |
+
return $options;
|
7942 |
+
}
|
7943 |
+
|
7944 |
+
throw new InvalidArgumentException("Invalid type for client options.");
|
7945 |
+
}
|
7946 |
+
|
7947 |
+
/**
|
7948 |
+
* Creates single or aggregate connections from different types of arguments
|
7949 |
+
* (string, array) or returns the passed argument if it is an instance of a
|
7950 |
+
* class implementing Predis\Connection\ConnectionInterface.
|
7951 |
+
*
|
7952 |
+
* Accepted types for connection parameters are:
|
7953 |
+
*
|
7954 |
+
* - Instance of Predis\Connection\ConnectionInterface.
|
7955 |
+
* - Instance of Predis\Connection\ParametersInterface.
|
7956 |
+
* - Array
|
7957 |
+
* - String
|
7958 |
+
* - Callable
|
7959 |
+
*
|
7960 |
+
* @param mixed $parameters Connection parameters or connection instance.
|
7961 |
+
*
|
7962 |
+
* @return ConnectionInterface
|
7963 |
+
*
|
7964 |
+
* @throws \InvalidArgumentException
|
7965 |
+
*/
|
7966 |
+
protected function createConnection($parameters)
|
7967 |
+
{
|
7968 |
+
if ($parameters instanceof ConnectionInterface) {
|
7969 |
+
return $parameters;
|
7970 |
+
}
|
7971 |
+
|
7972 |
+
if ($parameters instanceof ParametersInterface || is_string($parameters)) {
|
7973 |
+
return $this->options->connections->create($parameters);
|
7974 |
+
}
|
7975 |
+
|
7976 |
+
if (is_array($parameters)) {
|
7977 |
+
if (!isset($parameters[0])) {
|
7978 |
+
return $this->options->connections->create($parameters);
|
7979 |
+
}
|
7980 |
+
|
7981 |
+
$options = $this->options;
|
7982 |
+
|
7983 |
+
if ($options->defined('aggregate')) {
|
7984 |
+
$initializer = $this->getConnectionInitializerWrapper($options->aggregate);
|
7985 |
+
$connection = $initializer($parameters, $options);
|
7986 |
+
} else {
|
7987 |
+
if ($options->defined('replication') && $replication = $options->replication) {
|
7988 |
+
$connection = $replication;
|
7989 |
+
} else {
|
7990 |
+
$connection = $options->cluster;
|
7991 |
+
}
|
7992 |
+
|
7993 |
+
$options->connections->aggregate($connection, $parameters);
|
7994 |
+
}
|
7995 |
+
|
7996 |
+
return $connection;
|
7997 |
+
}
|
7998 |
+
|
7999 |
+
if (is_callable($parameters)) {
|
8000 |
+
$initializer = $this->getConnectionInitializerWrapper($parameters);
|
8001 |
+
$connection = $initializer($this->options);
|
8002 |
+
|
8003 |
+
return $connection;
|
8004 |
+
}
|
8005 |
+
|
8006 |
+
throw new InvalidArgumentException('Invalid type for connection parameters.');
|
8007 |
+
}
|
8008 |
+
|
8009 |
+
/**
|
8010 |
+
* Wraps a callable to make sure that its returned value represents a valid
|
8011 |
+
* connection type.
|
8012 |
+
*
|
8013 |
+
* @param mixed $callable
|
8014 |
+
*
|
8015 |
+
* @return \Closure
|
8016 |
+
*/
|
8017 |
+
protected function getConnectionInitializerWrapper($callable)
|
8018 |
+
{
|
8019 |
+
return function () use ($callable) {
|
8020 |
+
$connection = call_user_func_array($callable, func_get_args());
|
8021 |
+
|
8022 |
+
if (!$connection instanceof ConnectionInterface) {
|
8023 |
+
throw new UnexpectedValueException(
|
8024 |
+
'The callable connection initializer returned an invalid type.'
|
8025 |
+
);
|
8026 |
+
}
|
8027 |
+
|
8028 |
+
return $connection;
|
8029 |
+
};
|
8030 |
+
}
|
8031 |
+
|
8032 |
+
/**
|
8033 |
+
* {@inheritdoc}
|
8034 |
+
*/
|
8035 |
+
public function getProfile()
|
8036 |
+
{
|
8037 |
+
return $this->profile;
|
8038 |
+
}
|
8039 |
+
|
8040 |
+
/**
|
8041 |
+
* {@inheritdoc}
|
8042 |
+
*/
|
8043 |
+
public function getOptions()
|
8044 |
+
{
|
8045 |
+
return $this->options;
|
8046 |
+
}
|
8047 |
+
|
8048 |
+
/**
|
8049 |
+
* Creates a new client instance for the specified connection ID or alias,
|
8050 |
+
* only when working with an aggregate connection (cluster, replication).
|
8051 |
+
* The new client instances uses the same options of the original one.
|
8052 |
+
*
|
8053 |
+
* @param string $connectionID Identifier of a connection.
|
8054 |
+
*
|
8055 |
+
* @return Client
|
8056 |
+
*
|
8057 |
+
* @throws \InvalidArgumentException
|
8058 |
+
*/
|
8059 |
+
public function getClientFor($connectionID)
|
8060 |
+
{
|
8061 |
+
if (!$connection = $this->getConnectionById($connectionID)) {
|
8062 |
+
throw new InvalidArgumentException("Invalid connection ID: $connectionID.");
|
8063 |
+
}
|
8064 |
+
|
8065 |
+
return new static($connection, $this->options);
|
8066 |
+
}
|
8067 |
+
|
8068 |
+
/**
|
8069 |
+
* Opens the underlying connection and connects to the server.
|
8070 |
+
*/
|
8071 |
+
public function connect()
|
8072 |
+
{
|
8073 |
+
$this->connection->connect();
|
8074 |
+
}
|
8075 |
+
|
8076 |
+
/**
|
8077 |
+
* Closes the underlying connection and disconnects from the server.
|
8078 |
+
*/
|
8079 |
+
public function disconnect()
|
8080 |
+
{
|
8081 |
+
$this->connection->disconnect();
|
8082 |
+
}
|
8083 |
+
|
8084 |
+
/**
|
8085 |
+
* Closes the underlying connection and disconnects from the server.
|
8086 |
+
*
|
8087 |
+
* This is the same as `Client::disconnect()` as it does not actually send
|
8088 |
+
* the `QUIT` command to Redis, but simply closes the connection.
|
8089 |
+
*/
|
8090 |
+
public function quit()
|
8091 |
+
{
|
8092 |
+
$this->disconnect();
|
8093 |
+
}
|
8094 |
+
|
8095 |
+
/**
|
8096 |
+
* Returns the current state of the underlying connection.
|
8097 |
+
*
|
8098 |
+
* @return bool
|
8099 |
+
*/
|
8100 |
+
public function isConnected()
|
8101 |
+
{
|
8102 |
+
return $this->connection->isConnected();
|
8103 |
+
}
|
8104 |
+
|
8105 |
+
/**
|
8106 |
+
* {@inheritdoc}
|
8107 |
+
*/
|
8108 |
+
public function getConnection()
|
8109 |
+
{
|
8110 |
+
return $this->connection;
|
8111 |
+
}
|
8112 |
+
|
8113 |
+
/**
|
8114 |
+
* Retrieves the specified connection from the aggregate connection when the
|
8115 |
+
* client is in cluster or replication mode.
|
8116 |
+
*
|
8117 |
+
* @param string $connectionID Index or alias of the single connection.
|
8118 |
+
*
|
8119 |
+
* @return Connection\NodeConnectionInterface
|
8120 |
+
*
|
8121 |
+
* @throws NotSupportedException
|
8122 |
+
*/
|
8123 |
+
public function getConnectionById($connectionID)
|
8124 |
+
{
|
8125 |
+
if (!$this->connection instanceof AggregateConnectionInterface) {
|
8126 |
+
throw new NotSupportedException(
|
8127 |
+
'Retrieving connections by ID is supported only by aggregate connections.'
|
8128 |
+
);
|
8129 |
+
}
|
8130 |
+
|
8131 |
+
return $this->connection->getConnectionById($connectionID);
|
8132 |
+
}
|
8133 |
+
|
8134 |
+
/**
|
8135 |
+
* Executes a command without filtering its arguments, parsing the response,
|
8136 |
+
* applying any prefix to keys or throwing exceptions on Redis errors even
|
8137 |
+
* regardless of client options.
|
8138 |
+
*
|
8139 |
+
* It is possibile to indentify Redis error responses from normal responses
|
8140 |
+
* using the second optional argument which is populated by reference.
|
8141 |
+
*
|
8142 |
+
* @param array $arguments Command arguments as defined by the command signature.
|
8143 |
+
* @param bool $error Set to TRUE when Redis returned an error response.
|
8144 |
+
*
|
8145 |
+
* @return mixed
|
8146 |
+
*/
|
8147 |
+
public function executeRaw(array $arguments, &$error = null)
|
8148 |
+
{
|
8149 |
+
$error = false;
|
8150 |
+
$response = $this->connection->executeCommand(
|
8151 |
+
new RawCommand($arguments)
|
8152 |
+
);
|
8153 |
+
|
8154 |
+
if ($response instanceof ResponseInterface) {
|
8155 |
+
if ($response instanceof ErrorResponseInterface) {
|
8156 |
+
$error = true;
|
8157 |
+
}
|
8158 |
+
|
8159 |
+
return (string) $response;
|
8160 |
+
}
|
8161 |
+
|
8162 |
+
return $response;
|
8163 |
+
}
|
8164 |
+
|
8165 |
+
/**
|
8166 |
+
* {@inheritdoc}
|
8167 |
+
*/
|
8168 |
+
public function __call($commandID, $arguments)
|
8169 |
+
{
|
8170 |
+
return $this->executeCommand(
|
8171 |
+
$this->createCommand($commandID, $arguments)
|
8172 |
+
);
|
8173 |
+
}
|
8174 |
+
|
8175 |
+
/**
|
8176 |
+
* {@inheritdoc}
|
8177 |
+
*/
|
8178 |
+
public function createCommand($commandID, $arguments = array())
|
8179 |
+
{
|
8180 |
+
return $this->profile->createCommand($commandID, $arguments);
|
8181 |
+
}
|
8182 |
+
|
8183 |
+
/**
|
8184 |
+
* {@inheritdoc}
|
8185 |
+
*/
|
8186 |
+
public function executeCommand(CommandInterface $command)
|
8187 |
+
{
|
8188 |
+
$response = $this->connection->executeCommand($command);
|
8189 |
+
|
8190 |
+
if ($response instanceof ResponseInterface) {
|
8191 |
+
if ($response instanceof ErrorResponseInterface) {
|
8192 |
+
$response = $this->onErrorResponse($command, $response);
|
8193 |
+
}
|
8194 |
+
|
8195 |
+
return $response;
|
8196 |
+
}
|
8197 |
+
|
8198 |
+
return $command->parseResponse($response);
|
8199 |
+
}
|
8200 |
+
|
8201 |
+
/**
|
8202 |
+
* Handles -ERR responses returned by Redis.
|
8203 |
+
*
|
8204 |
+
* @param CommandInterface $command Redis command that generated the error.
|
8205 |
+
* @param ErrorResponseInterface $response Instance of the error response.
|
8206 |
+
*
|
8207 |
+
* @return mixed
|
8208 |
+
*
|
8209 |
+
* @throws ServerException
|
8210 |
+
*/
|
8211 |
+
protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
|
8212 |
+
{
|
8213 |
+
if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
|
8214 |
+
$eval = $this->createCommand('EVAL');
|
8215 |
+
$eval->setRawArguments($command->getEvalArguments());
|
8216 |
+
|
8217 |
+
$response = $this->executeCommand($eval);
|
8218 |
+
|
8219 |
+
if (!$response instanceof ResponseInterface) {
|
8220 |
+
$response = $command->parseResponse($response);
|
8221 |
+
}
|
8222 |
+
|
8223 |
+
return $response;
|
8224 |
+
}
|
8225 |
+
|
8226 |
+
if ($this->options->exceptions) {
|
8227 |
+
throw new ServerException($response->getMessage());
|
8228 |
+
}
|
8229 |
+
|
8230 |
+
return $response;
|
8231 |
+
}
|
8232 |
+
|
8233 |
+
/**
|
8234 |
+
* Executes the specified initializer method on `$this` by adjusting the
|
8235 |
+
* actual invokation depending on the arity (0, 1 or 2 arguments). This is
|
8236 |
+
* simply an utility method to create Redis contexts instances since they
|
8237 |
+
* follow a common initialization path.
|
8238 |
+
*
|
8239 |
+
* @param string $initializer Method name.
|
8240 |
+
* @param array $argv Arguments for the method.
|
8241 |
+
*
|
8242 |
+
* @return mixed
|
8243 |
+
*/
|
8244 |
+
private function sharedContextFactory($initializer, $argv = null)
|
8245 |
+
{
|
8246 |
+
switch (count($argv)) {
|
8247 |
+
case 0:
|
8248 |
+
return $this->$initializer();
|
8249 |
+
|
8250 |
+
case 1:
|
8251 |
+
return is_array($argv[0])
|
8252 |
+
? $this->$initializer($argv[0])
|
8253 |
+
: $this->$initializer(null, $argv[0]);
|
8254 |
+
|
8255 |
+
case 2:
|
8256 |
+
list($arg0, $arg1) = $argv;
|
8257 |
+
|
8258 |
+
return $this->$initializer($arg0, $arg1);
|
8259 |
+
|
8260 |
+
default:
|
8261 |
+
return $this->$initializer($this, $argv);
|
8262 |
+
}
|
8263 |
+
}
|
8264 |
+
|
8265 |
+
/**
|
8266 |
+
* Creates a new pipeline context and returns it, or returns the results of
|
8267 |
+
* a pipeline executed inside the optionally provided callable object.
|
8268 |
+
*
|
8269 |
+
* @param mixed ... Array of options, a callable for execution, or both.
|
8270 |
+
*
|
8271 |
+
* @return Pipeline|array
|
8272 |
+
*/
|
8273 |
+
public function pipeline(/* arguments */)
|
8274 |
+
{
|
8275 |
+
return $this->sharedContextFactory('createPipeline', func_get_args());
|
8276 |
+
}
|
8277 |
+
|
8278 |
+
/**
|
8279 |
+
* Actual pipeline context initializer method.
|
8280 |
+
*
|
8281 |
+
* @param array $options Options for the context.
|
8282 |
+
* @param mixed $callable Optional callable used to execute the context.
|
8283 |
+
*
|
8284 |
+
* @return Pipeline|array
|
8285 |
+
*/
|
8286 |
+
protected function createPipeline(array $options = null, $callable = null)
|
8287 |
+
{
|
8288 |
+
if (isset($options['atomic']) && $options['atomic']) {
|
8289 |
+
$class = 'Predis\Pipeline\Atomic';
|
8290 |
+
} elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
|
8291 |
+
$class = 'Predis\Pipeline\FireAndForget';
|
8292 |
+
} else {
|
8293 |
+
$class = 'Predis\Pipeline\Pipeline';
|
8294 |
+
}
|
8295 |
+
|
8296 |
+
/*
|
8297 |
+
* @var ClientContextInterface
|
8298 |
+
*/
|
8299 |
+
$pipeline = new $class($this);
|
8300 |
+
|
8301 |
+
if (isset($callable)) {
|
8302 |
+
return $pipeline->execute($callable);
|
8303 |
+
}
|
8304 |
+
|
8305 |
+
return $pipeline;
|
8306 |
+
}
|
8307 |
+
|
8308 |
+
/**
|
8309 |
+
* Creates a new transaction context and returns it, or returns the results
|
8310 |
+
* of a transaction executed inside the optionally provided callable object.
|
8311 |
+
*
|
8312 |
+
* @param mixed ... Array of options, a callable for execution, or both.
|
8313 |
+
*
|
8314 |
+
* @return MultiExecTransaction|array
|
8315 |
+
*/
|
8316 |
+
public function transaction(/* arguments */)
|
8317 |
+
{
|
8318 |
+
return $this->sharedContextFactory('createTransaction', func_get_args());
|
8319 |
+
}
|
8320 |
+
|
8321 |
+
/**
|
8322 |
+
* Actual transaction context initializer method.
|
8323 |
+
*
|
8324 |
+
* @param array $options Options for the context.
|
8325 |
+
* @param mixed $callable Optional callable used to execute the context.
|
8326 |
+
*
|
8327 |
+
* @return MultiExecTransaction|array
|
8328 |
+
*/
|
8329 |
+
protected function createTransaction(array $options = null, $callable = null)
|
8330 |
+
{
|
8331 |
+
$transaction = new MultiExecTransaction($this, $options);
|
8332 |
+
|
8333 |
+
if (isset($callable)) {
|
8334 |
+
return $transaction->execute($callable);
|
8335 |
+
}
|
8336 |
+
|
8337 |
+
return $transaction;
|
8338 |
+
}
|
8339 |
+
|
8340 |
+
/**
|
8341 |
+
* Creates a new publis/subscribe context and returns it, or starts its loop
|
8342 |
+
* inside the optionally provided callable object.
|
8343 |
+
*
|
8344 |
+
* @param mixed ... Array of options, a callable for execution, or both.
|
8345 |
+
*
|
8346 |
+
* @return PubSubConsumer|null
|
8347 |
+
*/
|
8348 |
+
public function pubSubLoop(/* arguments */)
|
8349 |
+
{
|
8350 |
+
return $this->sharedContextFactory('createPubSub', func_get_args());
|
8351 |
+
}
|
8352 |
+
|
8353 |
+
/**
|
8354 |
+
* Actual publish/subscribe context initializer method.
|
8355 |
+
*
|
8356 |
+
* @param array $options Options for the context.
|
8357 |
+
* @param mixed $callable Optional callable used to execute the context.
|
8358 |
+
*
|
8359 |
+
* @return PubSubConsumer|null
|
8360 |
+
*/
|
8361 |
+
protected function createPubSub(array $options = null, $callable = null)
|
8362 |
+
{
|
8363 |
+
$pubsub = new PubSubConsumer($this, $options);
|
8364 |
+
|
8365 |
+
if (!isset($callable)) {
|
8366 |
+
return $pubsub;
|
8367 |
+
}
|
8368 |
+
|
8369 |
+
foreach ($pubsub as $message) {
|
8370 |
+
if (call_user_func($callable, $pubsub, $message) === false) {
|
8371 |
+
$pubsub->stop();
|
8372 |
+
}
|
8373 |
+
}
|
8374 |
+
}
|
8375 |
+
|
8376 |
+
/**
|
8377 |
+
* Creates a new monitor consumer and returns it.
|
8378 |
+
*
|
8379 |
+
* @return MonitorConsumer
|
8380 |
+
*/
|
8381 |
+
public function monitor()
|
8382 |
+
{
|
8383 |
+
return new MonitorConsumer($this);
|
8384 |
+
}
|
8385 |
+
}
|
8386 |
+
|
8387 |
+
/**
|
8388 |
+
* Implements a lightweight PSR-0 compliant autoloader for Predis.
|
8389 |
+
*
|
8390 |
+
* @author Eric Naeseth <eric@thumbtack.com>
|
8391 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8392 |
+
*/
|
8393 |
+
class Autoloader
|
8394 |
+
{
|
8395 |
+
private $directory;
|
8396 |
+
private $prefix;
|
8397 |
+
private $prefixLength;
|
8398 |
+
|
8399 |
+
/**
|
8400 |
+
* @param string $baseDirectory Base directory where the source files are located.
|
8401 |
+
*/
|
8402 |
+
public function __construct($baseDirectory = __DIR__)
|
8403 |
+
{
|
8404 |
+
$this->directory = $baseDirectory;
|
8405 |
+
$this->prefix = __NAMESPACE__ . '\\';
|
8406 |
+
$this->prefixLength = strlen($this->prefix);
|
8407 |
+
}
|
8408 |
+
|
8409 |
+
/**
|
8410 |
+
* Registers the autoloader class with the PHP SPL autoloader.
|
8411 |
+
*
|
8412 |
+
* @param bool $prepend Prepend the autoloader on the stack instead of appending it.
|
8413 |
+
*/
|
8414 |
+
public static function register($prepend = false)
|
8415 |
+
{
|
8416 |
+
spl_autoload_register(array(new self, 'autoload'), true, $prepend);
|
8417 |
+
}
|
8418 |
+
|
8419 |
+
/**
|
8420 |
+
* Loads a class from a file using its fully qualified name.
|
8421 |
+
*
|
8422 |
+
* @param string $className Fully qualified name of a class.
|
8423 |
+
*/
|
8424 |
+
public function autoload($className)
|
8425 |
+
{
|
8426 |
+
if (0 === strpos($className, $this->prefix)) {
|
8427 |
+
$parts = explode('\\', substr($className, $this->prefixLength));
|
8428 |
+
$filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
|
8429 |
+
|
8430 |
+
if (is_file($filepath)) {
|
8431 |
+
require($filepath);
|
8432 |
+
}
|
8433 |
+
}
|
8434 |
+
}
|
8435 |
+
}
|
8436 |
+
|
8437 |
+
/* --------------------------------------------------------------------------- */
|
8438 |
+
|
8439 |
+
namespace Predis\Configuration;
|
8440 |
+
|
8441 |
+
use InvalidArgumentException;
|
8442 |
+
use Predis\Connection\Aggregate\ClusterInterface;
|
8443 |
+
use Predis\Connection\Aggregate\PredisCluster;
|
8444 |
+
use Predis\Connection\Aggregate\RedisCluster;
|
8445 |
+
use Predis\Connection\Factory;
|
8446 |
+
use Predis\Connection\FactoryInterface;
|
8447 |
+
use Predis\Command\Processor\KeyPrefixProcessor;
|
8448 |
+
use Predis\Command\Processor\ProcessorInterface;
|
8449 |
+
use Predis\Profile\Factory as Predis_Factory;
|
8450 |
+
use Predis\Profile\ProfileInterface;
|
8451 |
+
use Predis\Profile\RedisProfile;
|
8452 |
+
use Predis\Connection\Aggregate\MasterSlaveReplication;
|
8453 |
+
use Predis\Connection\Aggregate\ReplicationInterface;
|
8454 |
+
|
8455 |
+
/**
|
8456 |
+
* Defines an handler used by Predis\Configuration\Options to filter, validate
|
8457 |
+
* or return default values for a given option.
|
8458 |
+
*
|
8459 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8460 |
+
*/
|
8461 |
+
interface OptionInterface
|
8462 |
+
{
|
8463 |
+
/**
|
8464 |
+
* Filters and validates the passed value.
|
8465 |
+
*
|
8466 |
+
* @param OptionsInterface $options Options container.
|
8467 |
+
* @param mixed $value Input value.
|
8468 |
+
*
|
8469 |
+
* @return mixed
|
8470 |
+
*/
|
8471 |
+
public function filter(OptionsInterface $options, $value);
|
8472 |
+
|
8473 |
+
/**
|
8474 |
+
* Returns the default value for the option.
|
8475 |
+
*
|
8476 |
+
* @param OptionsInterface $options Options container.
|
8477 |
+
*
|
8478 |
+
* @return mixed
|
8479 |
+
*/
|
8480 |
+
public function getDefault(OptionsInterface $options);
|
8481 |
+
}
|
8482 |
+
|
8483 |
+
/**
|
8484 |
+
* Interface defining a container for client options.
|
8485 |
+
*
|
8486 |
+
* @property-read mixed aggregate Custom connection aggregator.
|
8487 |
+
* @property-read mixed cluster Aggregate connection for clustering.
|
8488 |
+
* @property-read mixed connections Connection factory.
|
8489 |
+
* @property-read mixed exceptions Toggles exceptions in client for -ERR responses.
|
8490 |
+
* @property-read mixed prefix Key prefixing strategy using the given prefix.
|
8491 |
+
* @property-read mixed profile Server profile.
|
8492 |
+
* @property-read mixed replication Aggregate connection for replication.
|
8493 |
+
*
|
8494 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8495 |
+
*/
|
8496 |
+
interface OptionsInterface
|
8497 |
+
{
|
8498 |
+
/**
|
8499 |
+
* Returns the default value for the given option.
|
8500 |
+
*
|
8501 |
+
* @param string $option Name of the option.
|
8502 |
+
*
|
8503 |
+
* @return mixed|null
|
8504 |
+
*/
|
8505 |
+
public function getDefault($option);
|
8506 |
+
|
8507 |
+
/**
|
8508 |
+
* Checks if the given option has been set by the user upon initialization.
|
8509 |
+
*
|
8510 |
+
* @param string $option Name of the option.
|
8511 |
+
*
|
8512 |
+
* @return bool
|
8513 |
+
*/
|
8514 |
+
public function defined($option);
|
8515 |
+
|
8516 |
+
/**
|
8517 |
+
* Checks if the given option has been set and does not evaluate to NULL.
|
8518 |
+
*
|
8519 |
+
* @param string $option Name of the option.
|
8520 |
+
*
|
8521 |
+
* @return bool
|
8522 |
+
*/
|
8523 |
+
public function __isset($option);
|
8524 |
+
|
8525 |
+
/**
|
8526 |
+
* Returns the value of the given option.
|
8527 |
+
*
|
8528 |
+
* @param string $option Name of the option.
|
8529 |
+
*
|
8530 |
+
* @return mixed|null
|
8531 |
+
*/
|
8532 |
+
public function __get($option);
|
8533 |
+
}
|
8534 |
+
|
8535 |
+
/**
|
8536 |
+
* Configures a command processor that apply the specified prefix string to a
|
8537 |
+
* series of Redis commands considered prefixable.
|
8538 |
+
*
|
8539 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8540 |
+
*/
|
8541 |
+
class PrefixOption implements OptionInterface
|
8542 |
+
{
|
8543 |
+
/**
|
8544 |
+
* {@inheritdoc}
|
8545 |
+
*/
|
8546 |
+
public function filter(OptionsInterface $options, $value)
|
8547 |
+
{
|
8548 |
+
if ($value instanceof ProcessorInterface) {
|
8549 |
+
return $value;
|
8550 |
+
}
|
8551 |
+
|
8552 |
+
return new KeyPrefixProcessor($value);
|
8553 |
+
}
|
8554 |
+
|
8555 |
+
/**
|
8556 |
+
* {@inheritdoc}
|
8557 |
+
*/
|
8558 |
+
public function getDefault(OptionsInterface $options)
|
8559 |
+
{
|
8560 |
+
// NOOP
|
8561 |
+
}
|
8562 |
+
}
|
8563 |
+
|
8564 |
+
/**
|
8565 |
+
* Configures the server profile to be used by the client to create command
|
8566 |
+
* instances depending on the specified version of the Redis server.
|
8567 |
+
*
|
8568 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8569 |
+
*/
|
8570 |
+
class ProfileOption implements OptionInterface
|
8571 |
+
{
|
8572 |
+
/**
|
8573 |
+
* Sets the commands processors that need to be applied to the profile.
|
8574 |
+
*
|
8575 |
+
* @param OptionsInterface $options Client options.
|
8576 |
+
* @param ProfileInterface $profile Server profile.
|
8577 |
+
*/
|
8578 |
+
protected function setProcessors(OptionsInterface $options, ProfileInterface $profile)
|
8579 |
+
{
|
8580 |
+
if (isset($options->prefix) && $profile instanceof RedisProfile) {
|
8581 |
+
// NOTE: directly using __get('prefix') is actually a workaround for
|
8582 |
+
// HHVM 2.3.0. It's correct and respects the options interface, it's
|
8583 |
+
// just ugly. We will remove this hack when HHVM will fix re-entrant
|
8584 |
+
// calls to __get() once and for all.
|
8585 |
+
|
8586 |
+
$profile->setProcessor($options->__get('prefix'));
|
8587 |
+
}
|
8588 |
+
}
|
8589 |
+
|
8590 |
+
/**
|
8591 |
+
* {@inheritdoc}
|
8592 |
+
*/
|
8593 |
+
public function filter(OptionsInterface $options, $value)
|
8594 |
+
{
|
8595 |
+
if (is_string($value)) {
|
8596 |
+
$value = Predis_Factory::get($value);
|
8597 |
+
$this->setProcessors($options, $value);
|
8598 |
+
} elseif (!$value instanceof ProfileInterface) {
|
8599 |
+
throw new InvalidArgumentException('Invalid value for the profile option.');
|
8600 |
+
}
|
8601 |
+
|
8602 |
+
return $value;
|
8603 |
+
}
|
8604 |
+
|
8605 |
+
/**
|
8606 |
+
* {@inheritdoc}
|
8607 |
+
*/
|
8608 |
+
public function getDefault(OptionsInterface $options)
|
8609 |
+
{
|
8610 |
+
$profile = Predis_Factory::getDefault();
|
8611 |
+
$this->setProcessors($options, $profile);
|
8612 |
+
|
8613 |
+
return $profile;
|
8614 |
+
}
|
8615 |
+
}
|
8616 |
+
|
8617 |
+
/**
|
8618 |
+
* Configures an aggregate connection used for master/slave replication among
|
8619 |
+
* multiple Redis nodes.
|
8620 |
+
*
|
8621 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8622 |
+
*/
|
8623 |
+
class ReplicationOption implements OptionInterface
|
8624 |
+
{
|
8625 |
+
/**
|
8626 |
+
* {@inheritdoc}
|
8627 |
+
*
|
8628 |
+
* @todo There's more code than needed due to a bug in filter_var() as
|
8629 |
+
* discussed here https://bugs.php.net/bug.php?id=49510 and different
|
8630 |
+
* behaviours when encountering NULL values on PHP 5.3.
|
8631 |
+
*/
|
8632 |
+
public function filter(OptionsInterface $options, $value)
|
8633 |
+
{
|
8634 |
+
if ($value instanceof ReplicationInterface) {
|
8635 |
+
return $value;
|
8636 |
+
}
|
8637 |
+
|
8638 |
+
if (is_bool($value) || $value === null) {
|
8639 |
+
return $value ? $this->getDefault($options) : null;
|
8640 |
+
}
|
8641 |
+
|
8642 |
+
if (
|
8643 |
+
!is_object($value) &&
|
8644 |
+
null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
|
8645 |
+
) {
|
8646 |
+
return $asbool ? $this->getDefault($options) : null;
|
8647 |
+
}
|
8648 |
+
|
8649 |
+
throw new InvalidArgumentException(
|
8650 |
+
"An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected."
|
8651 |
+
);
|
8652 |
+
}
|
8653 |
+
|
8654 |
+
/**
|
8655 |
+
* {@inheritdoc}
|
8656 |
+
*/
|
8657 |
+
public function getDefault(OptionsInterface $options)
|
8658 |
+
{
|
8659 |
+
return new MasterSlaveReplication();
|
8660 |
+
}
|
8661 |
+
}
|
8662 |
+
|
8663 |
+
/**
|
8664 |
+
* Manages Predis options with filtering, conversion and lazy initialization of
|
8665 |
+
* values using a mini-DI container approach.
|
8666 |
+
*
|
8667 |
+
* {@inheritdoc}
|
8668 |
+
*
|
8669 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8670 |
+
*/
|
8671 |
+
class Options implements OptionsInterface
|
8672 |
+
{
|
8673 |
+
protected $input;
|
8674 |
+
protected $options;
|
8675 |
+
protected $handlers;
|
8676 |
+
|
8677 |
+
/**
|
8678 |
+
* @param array $options Array of options with their values
|
8679 |
+
*/
|
8680 |
+
public function __construct(array $options = array())
|
8681 |
+
{
|
8682 |
+
$this->input = $options;
|
8683 |
+
$this->options = array();
|
8684 |
+
$this->handlers = $this->getHandlers();
|
8685 |
+
}
|
8686 |
+
|
8687 |
+
/**
|
8688 |
+
* Ensures that the default options are initialized.
|
8689 |
+
*
|
8690 |
+
* @return array
|
8691 |
+
*/
|
8692 |
+
protected function getHandlers()
|
8693 |
+
{
|
8694 |
+
return array(
|
8695 |
+
'cluster' => 'Predis\Configuration\ClusterOption',
|
8696 |
+
'connections' => 'Predis\Configuration\ConnectionFactoryOption',
|
8697 |
+
'exceptions' => 'Predis\Configuration\ExceptionsOption',
|
8698 |
+
'prefix' => 'Predis\Configuration\PrefixOption',
|
8699 |
+
'profile' => 'Predis\Configuration\ProfileOption',
|
8700 |
+
'replication' => 'Predis\Configuration\ReplicationOption',
|
8701 |
+
);
|
8702 |
+
}
|
8703 |
+
|
8704 |
+
/**
|
8705 |
+
* {@inheritdoc}
|
8706 |
+
*/
|
8707 |
+
public function getDefault($option)
|
8708 |
+
{
|
8709 |
+
if (isset($this->handlers[$option])) {
|
8710 |
+
$handler = $this->handlers[$option];
|
8711 |
+
$handler = new $handler();
|
8712 |
+
|
8713 |
+
return $handler->getDefault($this);
|
8714 |
+
}
|
8715 |
+
}
|
8716 |
+
|
8717 |
+
/**
|
8718 |
+
* {@inheritdoc}
|
8719 |
+
*/
|
8720 |
+
public function defined($option)
|
8721 |
+
{
|
8722 |
+
return (
|
8723 |
+
array_key_exists($option, $this->options) ||
|
8724 |
+
array_key_exists($option, $this->input)
|
8725 |
+
);
|
8726 |
+
}
|
8727 |
+
|
8728 |
+
/**
|
8729 |
+
* {@inheritdoc}
|
8730 |
+
*/
|
8731 |
+
public function __isset($option)
|
8732 |
+
{
|
8733 |
+
return (
|
8734 |
+
array_key_exists($option, $this->options) ||
|
8735 |
+
array_key_exists($option, $this->input)
|
8736 |
+
) && $this->__get($option) !== null;
|
8737 |
+
}
|
8738 |
+
|
8739 |
+
/**
|
8740 |
+
* {@inheritdoc}
|
8741 |
+
*/
|
8742 |
+
public function __get($option)
|
8743 |
+
{
|
8744 |
+
if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
|
8745 |
+
return $this->options[$option];
|
8746 |
+
}
|
8747 |
+
|
8748 |
+
if (isset($this->input[$option]) || array_key_exists($option, $this->input)) {
|
8749 |
+
$value = $this->input[$option];
|
8750 |
+
unset($this->input[$option]);
|
8751 |
+
|
8752 |
+
if (method_exists($value, '__invoke')) {
|
8753 |
+
$value = $value($this, $option);
|
8754 |
+
}
|
8755 |
+
|
8756 |
+
if (isset($this->handlers[$option])) {
|
8757 |
+
$handler = $this->handlers[$option];
|
8758 |
+
$handler = new $handler();
|
8759 |
+
$value = $handler->filter($this, $value);
|
8760 |
+
}
|
8761 |
+
|
8762 |
+
return $this->options[$option] = $value;
|
8763 |
+
}
|
8764 |
+
|
8765 |
+
if (isset($this->handlers[$option])) {
|
8766 |
+
return $this->options[$option] = $this->getDefault($option);
|
8767 |
+
}
|
8768 |
+
|
8769 |
+
return null;
|
8770 |
+
}
|
8771 |
+
}
|
8772 |
+
|
8773 |
+
/**
|
8774 |
+
* Configures a connection factory used by the client to create new connection
|
8775 |
+
* instances for single Redis nodes.
|
8776 |
+
*
|
8777 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8778 |
+
*/
|
8779 |
+
class ConnectionFactoryOption implements OptionInterface
|
8780 |
+
{
|
8781 |
+
/**
|
8782 |
+
* {@inheritdoc}
|
8783 |
+
*/
|
8784 |
+
public function filter(OptionsInterface $options, $value)
|
8785 |
+
{
|
8786 |
+
if ($value instanceof FactoryInterface) {
|
8787 |
+
return $value;
|
8788 |
+
} elseif (is_array($value)) {
|
8789 |
+
$factory = $this->getDefault($options);
|
8790 |
+
|
8791 |
+
foreach ($value as $scheme => $initializer) {
|
8792 |
+
$factory->define($scheme, $initializer);
|
8793 |
+
}
|
8794 |
+
|
8795 |
+
return $factory;
|
8796 |
+
} else {
|
8797 |
+
throw new InvalidArgumentException(
|
8798 |
+
'Invalid value provided for the connections option.'
|
8799 |
+
);
|
8800 |
+
}
|
8801 |
+
}
|
8802 |
+
|
8803 |
+
/**
|
8804 |
+
* {@inheritdoc}
|
8805 |
+
*/
|
8806 |
+
public function getDefault(OptionsInterface $options)
|
8807 |
+
{
|
8808 |
+
return new Factory();
|
8809 |
+
}
|
8810 |
+
}
|
8811 |
+
|
8812 |
+
/**
|
8813 |
+
* Configures whether consumers (such as the client) should throw exceptions on
|
8814 |
+
* Redis errors (-ERR responses) or just return instances of error responses.
|
8815 |
+
*
|
8816 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8817 |
+
*/
|
8818 |
+
class ExceptionsOption implements OptionInterface
|
8819 |
+
{
|
8820 |
+
/**
|
8821 |
+
* {@inheritdoc}
|
8822 |
+
*/
|
8823 |
+
public function filter(OptionsInterface $options, $value)
|
8824 |
+
{
|
8825 |
+
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
8826 |
+
}
|
8827 |
+
|
8828 |
+
/**
|
8829 |
+
* {@inheritdoc}
|
8830 |
+
*/
|
8831 |
+
public function getDefault(OptionsInterface $options)
|
8832 |
+
{
|
8833 |
+
return true;
|
8834 |
+
}
|
8835 |
+
}
|
8836 |
+
|
8837 |
+
/**
|
8838 |
+
* Configures an aggregate connection used for clustering
|
8839 |
+
* multiple Redis nodes using various implementations with
|
8840 |
+
* different algorithms or strategies.
|
8841 |
+
*
|
8842 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8843 |
+
*/
|
8844 |
+
class ClusterOption implements OptionInterface
|
8845 |
+
{
|
8846 |
+
/**
|
8847 |
+
* Creates a new cluster connection from on a known descriptive name.
|
8848 |
+
*
|
8849 |
+
* @param OptionsInterface $options Instance of the client options.
|
8850 |
+
* @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`)
|
8851 |
+
*
|
8852 |
+
* @return ClusterInterface|null
|
8853 |
+
*/
|
8854 |
+
protected function createByDescription(OptionsInterface $options, $id)
|
8855 |
+
{
|
8856 |
+
switch ($id) {
|
8857 |
+
case 'predis':
|
8858 |
+
case 'predis-cluster':
|
8859 |
+
return new PredisCluster();
|
8860 |
+
|
8861 |
+
case 'redis':
|
8862 |
+
case 'redis-cluster':
|
8863 |
+
return new RedisCluster($options->connections);
|
8864 |
+
|
8865 |
+
default:
|
8866 |
+
return;
|
8867 |
+
}
|
8868 |
+
}
|
8869 |
+
|
8870 |
+
/**
|
8871 |
+
* {@inheritdoc}
|
8872 |
+
*/
|
8873 |
+
public function filter(OptionsInterface $options, $value)
|
8874 |
+
{
|
8875 |
+
if (is_string($value)) {
|
8876 |
+
$value = $this->createByDescription($options, $value);
|
8877 |
+
}
|
8878 |
+
|
8879 |
+
if (!$value instanceof ClusterInterface) {
|
8880 |
+
throw new InvalidArgumentException(
|
8881 |
+
"An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected."
|
8882 |
+
);
|
8883 |
+
}
|
8884 |
+
|
8885 |
+
return $value;
|
8886 |
+
}
|
8887 |
+
|
8888 |
+
/**
|
8889 |
+
* {@inheritdoc}
|
8890 |
+
*/
|
8891 |
+
public function getDefault(OptionsInterface $options)
|
8892 |
+
{
|
8893 |
+
return new PredisCluster();
|
8894 |
+
}
|
8895 |
+
}
|
8896 |
+
|
8897 |
+
/* --------------------------------------------------------------------------- */
|
8898 |
+
|
8899 |
+
namespace Predis\Response;
|
8900 |
+
|
8901 |
+
use Predis\PredisException;
|
8902 |
+
|
8903 |
+
/**
|
8904 |
+
* Represents a complex response object from Redis.
|
8905 |
+
*
|
8906 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8907 |
+
*/
|
8908 |
+
interface ResponseInterface
|
8909 |
+
{
|
8910 |
+
}
|
8911 |
+
|
8912 |
+
/**
|
8913 |
+
* Represents an error returned by Redis (responses identified by "-" in the
|
8914 |
+
* Redis protocol) during the execution of an operation on the server.
|
8915 |
+
*
|
8916 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8917 |
+
*/
|
8918 |
+
interface ErrorInterface extends ResponseInterface
|
8919 |
+
{
|
8920 |
+
/**
|
8921 |
+
* Returns the error message
|
8922 |
+
*
|
8923 |
+
* @return string
|
8924 |
+
*/
|
8925 |
+
public function getMessage();
|
8926 |
+
|
8927 |
+
/**
|
8928 |
+
* Returns the error type (e.g. ERR, ASK, MOVED)
|
8929 |
+
*
|
8930 |
+
* @return string
|
8931 |
+
*/
|
8932 |
+
public function getErrorType();
|
8933 |
+
}
|
8934 |
+
|
8935 |
+
/**
|
8936 |
+
* Represents a status response returned by Redis.
|
8937 |
+
*
|
8938 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
8939 |
+
*/
|
8940 |
+
class Status implements ResponseInterface
|
8941 |
+
{
|
8942 |
+
private static $OK;
|
8943 |
+
private static $QUEUED;
|
8944 |
+
|
8945 |
+
private $payload;
|
8946 |
+
|
8947 |
+
/**
|
8948 |
+
* @param string $payload Payload of the status response as returned by Redis.
|
8949 |
+
*/
|
8950 |
+
public function __construct($payload)
|
8951 |
+
{
|
8952 |
+
$this->payload = $payload;
|
8953 |
+
}
|
8954 |
+
|
8955 |
+
/**
|
8956 |
+
* Converts the response object to its string representation.
|
8957 |
+
*
|
8958 |
+
* @return string
|
8959 |
+
*/
|
8960 |
+
public function __toString()
|
8961 |
+
{
|
8962 |
+
return $this->payload;
|
8963 |
+
}
|
8964 |
+
|
8965 |
+
/**
|
8966 |
+
* Returns the payload of status response.
|
8967 |
+
*
|
8968 |
+
* @return string
|
8969 |
+
*/
|
8970 |
+
public function getPayload()
|
8971 |
+
{
|
8972 |
+
return $this->payload;
|
8973 |
+
}
|
8974 |
+
|
8975 |
+
/**
|
8976 |
+
* Returns an instance of a status response object.
|
8977 |
+
*
|
8978 |
+
* Common status responses such as OK or QUEUED are cached in order to lower
|
8979 |
+
* the global memory usage especially when using pipelines.
|
8980 |
+
*
|
8981 |
+
* @param string $payload Status response payload.
|
8982 |
+
*
|
8983 |
+
* @return string
|
8984 |
+
*/
|
8985 |
+
public static function get($payload)
|
8986 |
+
{
|
8987 |
+
switch ($payload) {
|
8988 |
+
case 'OK':
|
8989 |
+
case 'QUEUED':
|
8990 |
+
if (isset(self::$$payload)) {
|
8991 |
+
return self::$$payload;
|
8992 |
+
}
|
8993 |
+
|
8994 |
+
return self::$$payload = new self($payload);
|
8995 |
+
|
8996 |
+
default:
|
8997 |
+
return new self($payload);
|
8998 |
+
}
|
8999 |
+
}
|
9000 |
+
}
|
9001 |
+
|
9002 |
+
/**
|
9003 |
+
* Represents an error returned by Redis (-ERR responses) during the execution
|
9004 |
+
* of a command on the server.
|
9005 |
+
*
|
9006 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9007 |
+
*/
|
9008 |
+
class Error implements ErrorInterface
|
9009 |
+
{
|
9010 |
+
private $message;
|
9011 |
+
|
9012 |
+
/**
|
9013 |
+
* @param string $message Error message returned by Redis
|
9014 |
+
*/
|
9015 |
+
public function __construct($message)
|
9016 |
+
{
|
9017 |
+
$this->message = $message;
|
9018 |
+
}
|
9019 |
+
|
9020 |
+
/**
|
9021 |
+
* {@inheritdoc}
|
9022 |
+
*/
|
9023 |
+
public function getMessage()
|
9024 |
+
{
|
9025 |
+
return $this->message;
|
9026 |
+
}
|
9027 |
+
|
9028 |
+
/**
|
9029 |
+
* {@inheritdoc}
|
9030 |
+
*/
|
9031 |
+
public function getErrorType()
|
9032 |
+
{
|
9033 |
+
list($errorType, ) = explode(' ', $this->getMessage(), 2);
|
9034 |
+
|
9035 |
+
return $errorType;
|
9036 |
+
}
|
9037 |
+
|
9038 |
+
/**
|
9039 |
+
* Converts the object to its string representation.
|
9040 |
+
*
|
9041 |
+
* @return string
|
9042 |
+
*/
|
9043 |
+
public function __toString()
|
9044 |
+
{
|
9045 |
+
return $this->getMessage();
|
9046 |
+
}
|
9047 |
+
}
|
9048 |
+
|
9049 |
+
/**
|
9050 |
+
* Exception class that identifies server-side Redis errors.
|
9051 |
+
*
|
9052 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9053 |
+
*/
|
9054 |
+
class ServerException extends PredisException implements ErrorInterface
|
9055 |
+
{
|
9056 |
+
/**
|
9057 |
+
* Gets the type of the error returned by Redis.
|
9058 |
+
*
|
9059 |
+
* @return string
|
9060 |
+
*/
|
9061 |
+
public function getErrorType()
|
9062 |
+
{
|
9063 |
+
list($errorType, ) = explode(' ', $this->getMessage(), 2);
|
9064 |
+
|
9065 |
+
return $errorType;
|
9066 |
+
}
|
9067 |
+
|
9068 |
+
/**
|
9069 |
+
* Converts the exception to an instance of Predis\Response\Error.
|
9070 |
+
*
|
9071 |
+
* @return Error
|
9072 |
+
*/
|
9073 |
+
public function toErrorResponse()
|
9074 |
+
{
|
9075 |
+
return new Error($this->getMessage());
|
9076 |
+
}
|
9077 |
+
}
|
9078 |
+
|
9079 |
+
/* --------------------------------------------------------------------------- */
|
9080 |
+
|
9081 |
+
namespace Predis\Protocol\Text\Handler;
|
9082 |
+
|
9083 |
+
use Predis\CommunicationException;
|
9084 |
+
use Predis\Connection\CompositeConnectionInterface;
|
9085 |
+
use Predis\Protocol\ProtocolException;
|
9086 |
+
use Predis\Response\Error;
|
9087 |
+
use Predis\Response\Status;
|
9088 |
+
use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
|
9089 |
+
|
9090 |
+
/**
|
9091 |
+
* Defines a pluggable handler used to parse a particular type of response.
|
9092 |
+
*
|
9093 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9094 |
+
*/
|
9095 |
+
interface ResponseHandlerInterface
|
9096 |
+
{
|
9097 |
+
/**
|
9098 |
+
* Deserializes a response returned by Redis and reads more data from the
|
9099 |
+
* connection if needed.
|
9100 |
+
*
|
9101 |
+
* @param CompositeConnectionInterface $connection Redis connection.
|
9102 |
+
* @param string $payload String payload.
|
9103 |
+
*
|
9104 |
+
* @return mixed
|
9105 |
+
*/
|
9106 |
+
public function handle(CompositeConnectionInterface $connection, $payload);
|
9107 |
+
}
|
9108 |
+
|
9109 |
+
/**
|
9110 |
+
* Handler for the status response type in the standard Redis wire protocol. It
|
9111 |
+
* translates certain classes of status response to PHP objects or just returns
|
9112 |
+
* the payload as a string.
|
9113 |
+
*
|
9114 |
+
* @link http://redis.io/topics/protocol
|
9115 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9116 |
+
*/
|
9117 |
+
class StatusResponse implements ResponseHandlerInterface
|
9118 |
+
{
|
9119 |
+
/**
|
9120 |
+
* {@inheritdoc}
|
9121 |
+
*/
|
9122 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9123 |
+
{
|
9124 |
+
return Status::get($payload);
|
9125 |
+
}
|
9126 |
+
}
|
9127 |
+
|
9128 |
+
/**
|
9129 |
+
* Handler for the multibulk response type in the standard Redis wire protocol.
|
9130 |
+
* It returns multibulk responses as iterators that can stream bulk elements.
|
9131 |
+
*
|
9132 |
+
* Streamable multibulk responses are not globally supported by the abstractions
|
9133 |
+
* built-in into Predis, such as transactions or pipelines. Use them with care!
|
9134 |
+
*
|
9135 |
+
* @link http://redis.io/topics/protocol
|
9136 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9137 |
+
*/
|
9138 |
+
class StreamableMultiBulkResponse implements ResponseHandlerInterface
|
9139 |
+
{
|
9140 |
+
/**
|
9141 |
+
* {@inheritdoc}
|
9142 |
+
*/
|
9143 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9144 |
+
{
|
9145 |
+
$length = (int) $payload;
|
9146 |
+
|
9147 |
+
if ("$length" != $payload) {
|
9148 |
+
CommunicationException::handle(new ProtocolException(
|
9149 |
+
$connection, "Cannot parse '$payload' as a valid length for a multi-bulk response."
|
9150 |
+
));
|
9151 |
+
}
|
9152 |
+
|
9153 |
+
return new MultiBulkIterator($connection, $length);
|
9154 |
+
}
|
9155 |
+
}
|
9156 |
+
|
9157 |
+
/**
|
9158 |
+
* Handler for the multibulk response type in the standard Redis wire protocol.
|
9159 |
+
* It returns multibulk responses as PHP arrays.
|
9160 |
+
*
|
9161 |
+
* @link http://redis.io/topics/protocol
|
9162 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9163 |
+
*/
|
9164 |
+
class MultiBulkResponse implements ResponseHandlerInterface
|
9165 |
+
{
|
9166 |
+
/**
|
9167 |
+
* {@inheritdoc}
|
9168 |
+
*/
|
9169 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9170 |
+
{
|
9171 |
+
$length = (int) $payload;
|
9172 |
+
|
9173 |
+
if ("$length" !== $payload) {
|
9174 |
+
CommunicationException::handle(new ProtocolException(
|
9175 |
+
$connection, "Cannot parse '$payload' as a valid length of a multi-bulk response."
|
9176 |
+
));
|
9177 |
+
}
|
9178 |
+
|
9179 |
+
if ($length === -1) {
|
9180 |
+
return null;
|
9181 |
+
}
|
9182 |
+
|
9183 |
+
$list = array();
|
9184 |
+
|
9185 |
+
if ($length > 0) {
|
9186 |
+
$handlersCache = array();
|
9187 |
+
$reader = $connection->getProtocol()->getResponseReader();
|
9188 |
+
|
9189 |
+
for ($i = 0; $i < $length; $i++) {
|
9190 |
+
$header = $connection->readLine();
|
9191 |
+
$prefix = $header[0];
|
9192 |
+
|
9193 |
+
if (isset($handlersCache[$prefix])) {
|
9194 |
+
$handler = $handlersCache[$prefix];
|
9195 |
+
} else {
|
9196 |
+
$handler = $reader->getHandler($prefix);
|
9197 |
+
$handlersCache[$prefix] = $handler;
|
9198 |
+
}
|
9199 |
+
|
9200 |
+
$list[$i] = $handler->handle($connection, substr($header, 1));
|
9201 |
+
}
|
9202 |
+
}
|
9203 |
+
|
9204 |
+
return $list;
|
9205 |
+
}
|
9206 |
+
}
|
9207 |
+
|
9208 |
+
/**
|
9209 |
+
* Handler for the error response type in the standard Redis wire protocol.
|
9210 |
+
* It translates the payload to a complex response object for Predis.
|
9211 |
+
*
|
9212 |
+
* @link http://redis.io/topics/protocol
|
9213 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9214 |
+
*/
|
9215 |
+
class ErrorResponse implements ResponseHandlerInterface
|
9216 |
+
{
|
9217 |
+
/**
|
9218 |
+
* {@inheritdoc}
|
9219 |
+
*/
|
9220 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9221 |
+
{
|
9222 |
+
return new Error($payload);
|
9223 |
+
}
|
9224 |
+
}
|
9225 |
+
|
9226 |
+
/**
|
9227 |
+
* Handler for the integer response type in the standard Redis wire protocol.
|
9228 |
+
* It translates the payload an integer or NULL.
|
9229 |
+
*
|
9230 |
+
* @link http://redis.io/topics/protocol
|
9231 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9232 |
+
*/
|
9233 |
+
class IntegerResponse implements ResponseHandlerInterface
|
9234 |
+
{
|
9235 |
+
/**
|
9236 |
+
* {@inheritdoc}
|
9237 |
+
*/
|
9238 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9239 |
+
{
|
9240 |
+
if (is_numeric($payload)) {
|
9241 |
+
return (int) $payload;
|
9242 |
+
}
|
9243 |
+
|
9244 |
+
if ($payload !== 'nil') {
|
9245 |
+
CommunicationException::handle(new ProtocolException(
|
9246 |
+
$connection, "Cannot parse '$payload' as a valid numeric response."
|
9247 |
+
));
|
9248 |
+
}
|
9249 |
+
|
9250 |
+
return null;
|
9251 |
+
}
|
9252 |
+
}
|
9253 |
+
|
9254 |
+
/**
|
9255 |
+
* Handler for the bulk response type in the standard Redis wire protocol.
|
9256 |
+
* It translates the payload to a string or a NULL.
|
9257 |
+
*
|
9258 |
+
* @link http://redis.io/topics/protocol
|
9259 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9260 |
+
*/
|
9261 |
+
class BulkResponse implements ResponseHandlerInterface
|
9262 |
+
{
|
9263 |
+
/**
|
9264 |
+
* {@inheritdoc}
|
9265 |
+
*/
|
9266 |
+
public function handle(CompositeConnectionInterface $connection, $payload)
|
9267 |
+
{
|
9268 |
+
$length = (int) $payload;
|
9269 |
+
|
9270 |
+
if ("$length" !== $payload) {
|
9271 |
+
CommunicationException::handle(new ProtocolException(
|
9272 |
+
$connection, "Cannot parse '$payload' as a valid length for a bulk response."
|
9273 |
+
));
|
9274 |
+
}
|
9275 |
+
|
9276 |
+
if ($length >= 0) {
|
9277 |
+
return substr($connection->readBuffer($length + 2), 0, -2);
|
9278 |
+
}
|
9279 |
+
|
9280 |
+
if ($length == -1) {
|
9281 |
+
return null;
|
9282 |
+
}
|
9283 |
+
|
9284 |
+
CommunicationException::handle(new ProtocolException(
|
9285 |
+
$connection, "Value '$payload' is not a valid length for a bulk response."
|
9286 |
+
));
|
9287 |
+
|
9288 |
+
return;
|
9289 |
+
}
|
9290 |
+
}
|
9291 |
+
|
9292 |
+
/* --------------------------------------------------------------------------- */
|
9293 |
+
|
9294 |
+
namespace Predis\Collection\Iterator;
|
9295 |
+
|
9296 |
+
use Iterator;
|
9297 |
+
use Predis\ClientInterface;
|
9298 |
+
use Predis\NotSupportedException;
|
9299 |
+
use InvalidArgumentException;
|
9300 |
+
|
9301 |
+
/**
|
9302 |
+
* Provides the base implementation for a fully-rewindable PHP iterator that can
|
9303 |
+
* incrementally iterate over cursor-based collections stored on Redis using the
|
9304 |
+
* commands in the `SCAN` family.
|
9305 |
+
*
|
9306 |
+
* Given their incremental nature with multiple fetches, these kind of iterators
|
9307 |
+
* offer limited guarantees about the returned elements because the collection
|
9308 |
+
* can change several times during the iteration process.
|
9309 |
+
*
|
9310 |
+
* @see http://redis.io/commands/scan
|
9311 |
+
*
|
9312 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9313 |
+
*/
|
9314 |
+
abstract class CursorBasedIterator implements Iterator
|
9315 |
+
{
|
9316 |
+
protected $client;
|
9317 |
+
protected $match;
|
9318 |
+
protected $count;
|
9319 |
+
|
9320 |
+
protected $valid;
|
9321 |
+
protected $fetchmore;
|
9322 |
+
protected $elements;
|
9323 |
+
protected $cursor;
|
9324 |
+
protected $position;
|
9325 |
+
protected $current;
|
9326 |
+
|
9327 |
+
/**
|
9328 |
+
* @param ClientInterface $client Client connected to Redis.
|
9329 |
+
* @param string $match Pattern to match during the server-side iteration.
|
9330 |
+
* @param int $count Hint used by Redis to compute the number of results per iteration.
|
9331 |
+
*/
|
9332 |
+
public function __construct(ClientInterface $client, $match = null, $count = null)
|
9333 |
+
{
|
9334 |
+
$this->client = $client;
|
9335 |
+
$this->match = $match;
|
9336 |
+
$this->count = $count;
|
9337 |
+
|
9338 |
+
$this->reset();
|
9339 |
+
}
|
9340 |
+
|
9341 |
+
/**
|
9342 |
+
* Ensures that the client supports the specified Redis command required to
|
9343 |
+
* fetch elements from the server to perform the iteration.
|
9344 |
+
*
|
9345 |
+
* @param ClientInterface $client Client connected to Redis.
|
9346 |
+
* @param string $commandID Command ID.
|
9347 |
+
*
|
9348 |
+
* @throws NotSupportedException
|
9349 |
+
*/
|
9350 |
+
protected function requiredCommand(ClientInterface $client, $commandID)
|
9351 |
+
{
|
9352 |
+
if (!$client->getProfile()->supportsCommand($commandID)) {
|
9353 |
+
throw new NotSupportedException("The current profile does not support '$commandID'.");
|
9354 |
+
}
|
9355 |
+
}
|
9356 |
+
|
9357 |
+
/**
|
9358 |
+
* Resets the inner state of the iterator.
|
9359 |
+
*/
|
9360 |
+
protected function reset()
|
9361 |
+
{
|
9362 |
+
$this->valid = true;
|
9363 |
+
$this->fetchmore = true;
|
9364 |
+
$this->elements = array();
|
9365 |
+
$this->cursor = 0;
|
9366 |
+
$this->position = -1;
|
9367 |
+
$this->current = null;
|
9368 |
+
}
|
9369 |
+
|
9370 |
+
/**
|
9371 |
+
* Returns an array of options for the `SCAN` command.
|
9372 |
+
*
|
9373 |
+
* @return array
|
9374 |
+
*/
|
9375 |
+
protected function getScanOptions()
|
9376 |
+
{
|
9377 |
+
$options = array();
|
9378 |
+
|
9379 |
+
if (strlen($this->match) > 0) {
|
9380 |
+
$options['MATCH'] = $this->match;
|
9381 |
+
}
|
9382 |
+
|
9383 |
+
if ($this->count > 0) {
|
9384 |
+
$options['COUNT'] = $this->count;
|
9385 |
+
}
|
9386 |
+
|
9387 |
+
return $options;
|
9388 |
+
}
|
9389 |
+
|
9390 |
+
/**
|
9391 |
+
* Fetches a new set of elements from the remote collection, effectively
|
9392 |
+
* advancing the iteration process.
|
9393 |
+
*
|
9394 |
+
* @return array
|
9395 |
+
*/
|
9396 |
+
abstract protected function executeCommand();
|
9397 |
+
|
9398 |
+
/**
|
9399 |
+
* Populates the local buffer of elements fetched from the server during
|
9400 |
+
* the iteration.
|
9401 |
+
*/
|
9402 |
+
protected function fetch()
|
9403 |
+
{
|
9404 |
+
list($cursor, $elements) = $this->executeCommand();
|
9405 |
+
|
9406 |
+
if (!$cursor) {
|
9407 |
+
$this->fetchmore = false;
|
9408 |
+
}
|
9409 |
+
|
9410 |
+
$this->cursor = $cursor;
|
9411 |
+
$this->elements = $elements;
|
9412 |
+
}
|
9413 |
+
|
9414 |
+
/**
|
9415 |
+
* Extracts next values for key() and current().
|
9416 |
+
*/
|
9417 |
+
protected function extractNext()
|
9418 |
+
{
|
9419 |
+
$this->position++;
|
9420 |
+
$this->current = array_shift($this->elements);
|
9421 |
+
}
|
9422 |
+
|
9423 |
+
/**
|
9424 |
+
* {@inheritdoc}
|
9425 |
+
*/
|
9426 |
+
public function rewind()
|
9427 |
+
{
|
9428 |
+
$this->reset();
|
9429 |
+
$this->next();
|
9430 |
+
}
|
9431 |
+
|
9432 |
+
/**
|
9433 |
+
* {@inheritdoc}
|
9434 |
+
*/
|
9435 |
+
public function current()
|
9436 |
+
{
|
9437 |
+
return $this->current;
|
9438 |
+
}
|
9439 |
+
|
9440 |
+
/**
|
9441 |
+
* {@inheritdoc}
|
9442 |
+
*/
|
9443 |
+
public function key()
|
9444 |
+
{
|
9445 |
+
return $this->position;
|
9446 |
+
}
|
9447 |
+
|
9448 |
+
/**
|
9449 |
+
* {@inheritdoc}
|
9450 |
+
*/
|
9451 |
+
public function next()
|
9452 |
+
{
|
9453 |
+
tryFetch: {
|
9454 |
+
if (!$this->elements && $this->fetchmore) {
|
9455 |
+
$this->fetch();
|
9456 |
+
}
|
9457 |
+
|
9458 |
+
if ($this->elements) {
|
9459 |
+
$this->extractNext();
|
9460 |
+
} elseif ($this->cursor) {
|
9461 |
+
goto tryFetch;
|
9462 |
+
} else {
|
9463 |
+
$this->valid = false;
|
9464 |
+
}
|
9465 |
+
}
|
9466 |
+
}
|
9467 |
+
|
9468 |
+
/**
|
9469 |
+
* {@inheritdoc}
|
9470 |
+
*/
|
9471 |
+
public function valid()
|
9472 |
+
{
|
9473 |
+
return $this->valid;
|
9474 |
+
}
|
9475 |
+
}
|
9476 |
+
|
9477 |
+
/**
|
9478 |
+
* Abstracts the iteration of members stored in a sorted set by leveraging the
|
9479 |
+
* ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
9480 |
+
*
|
9481 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9482 |
+
* @link http://redis.io/commands/scan
|
9483 |
+
*/
|
9484 |
+
class SortedSetKey extends CursorBasedIterator
|
9485 |
+
{
|
9486 |
+
protected $key;
|
9487 |
+
|
9488 |
+
/**
|
9489 |
+
* {@inheritdoc}
|
9490 |
+
*/
|
9491 |
+
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
9492 |
+
{
|
9493 |
+
$this->requiredCommand($client, 'ZSCAN');
|
9494 |
+
|
9495 |
+
parent::__construct($client, $match, $count);
|
9496 |
+
|
9497 |
+
$this->key = $key;
|
9498 |
+
}
|
9499 |
+
|
9500 |
+
/**
|
9501 |
+
* {@inheritdoc}
|
9502 |
+
*/
|
9503 |
+
protected function executeCommand()
|
9504 |
+
{
|
9505 |
+
return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
|
9506 |
+
}
|
9507 |
+
|
9508 |
+
/**
|
9509 |
+
* {@inheritdoc}
|
9510 |
+
*/
|
9511 |
+
protected function extractNext()
|
9512 |
+
{
|
9513 |
+
if ($kv = each($this->elements)) {
|
9514 |
+
$this->position = $kv[0];
|
9515 |
+
$this->current = $kv[1];
|
9516 |
+
|
9517 |
+
unset($this->elements[$this->position]);
|
9518 |
+
}
|
9519 |
+
}
|
9520 |
+
}
|
9521 |
+
|
9522 |
+
/**
|
9523 |
+
* Abstracts the iteration of members stored in a set by leveraging the SSCAN
|
9524 |
+
* command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
9525 |
+
*
|
9526 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9527 |
+
* @link http://redis.io/commands/scan
|
9528 |
+
*/
|
9529 |
+
class SetKey extends CursorBasedIterator
|
9530 |
+
{
|
9531 |
+
protected $key;
|
9532 |
+
|
9533 |
+
/**
|
9534 |
+
* {@inheritdoc}
|
9535 |
+
*/
|
9536 |
+
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
9537 |
+
{
|
9538 |
+
$this->requiredCommand($client, 'SSCAN');
|
9539 |
+
|
9540 |
+
parent::__construct($client, $match, $count);
|
9541 |
+
|
9542 |
+
$this->key = $key;
|
9543 |
+
}
|
9544 |
+
|
9545 |
+
/**
|
9546 |
+
* {@inheritdoc}
|
9547 |
+
*/
|
9548 |
+
protected function executeCommand()
|
9549 |
+
{
|
9550 |
+
return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
|
9551 |
+
}
|
9552 |
+
}
|
9553 |
+
|
9554 |
+
/**
|
9555 |
+
* Abstracts the iteration of the keyspace on a Redis instance by leveraging the
|
9556 |
+
* SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
9557 |
+
*
|
9558 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9559 |
+
* @link http://redis.io/commands/scan
|
9560 |
+
*/
|
9561 |
+
class Keyspace extends CursorBasedIterator
|
9562 |
+
{
|
9563 |
+
/**
|
9564 |
+
* {@inheritdoc}
|
9565 |
+
*/
|
9566 |
+
public function __construct(ClientInterface $client, $match = null, $count = null)
|
9567 |
+
{
|
9568 |
+
$this->requiredCommand($client, 'SCAN');
|
9569 |
+
|
9570 |
+
parent::__construct($client, $match, $count);
|
9571 |
+
}
|
9572 |
+
|
9573 |
+
/**
|
9574 |
+
* {@inheritdoc}
|
9575 |
+
*/
|
9576 |
+
protected function executeCommand()
|
9577 |
+
{
|
9578 |
+
return $this->client->scan($this->cursor, $this->getScanOptions());
|
9579 |
+
}
|
9580 |
+
}
|
9581 |
+
|
9582 |
+
/**
|
9583 |
+
* Abstracts the iteration of fields and values of an hash by leveraging the
|
9584 |
+
* HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
|
9585 |
+
*
|
9586 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9587 |
+
* @link http://redis.io/commands/scan
|
9588 |
+
*/
|
9589 |
+
class HashKey extends CursorBasedIterator
|
9590 |
+
{
|
9591 |
+
protected $key;
|
9592 |
+
|
9593 |
+
/**
|
9594 |
+
* {@inheritdoc}
|
9595 |
+
*/
|
9596 |
+
public function __construct(ClientInterface $client, $key, $match = null, $count = null)
|
9597 |
+
{
|
9598 |
+
$this->requiredCommand($client, 'HSCAN');
|
9599 |
+
|
9600 |
+
parent::__construct($client, $match, $count);
|
9601 |
+
|
9602 |
+
$this->key = $key;
|
9603 |
+
}
|
9604 |
+
|
9605 |
+
/**
|
9606 |
+
* {@inheritdoc}
|
9607 |
+
*/
|
9608 |
+
protected function executeCommand()
|
9609 |
+
{
|
9610 |
+
return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
|
9611 |
+
}
|
9612 |
+
|
9613 |
+
/**
|
9614 |
+
* {@inheritdoc}
|
9615 |
+
*/
|
9616 |
+
protected function extractNext()
|
9617 |
+
{
|
9618 |
+
$this->position = key($this->elements);
|
9619 |
+
$this->current = array_shift($this->elements);
|
9620 |
+
}
|
9621 |
+
}
|
9622 |
+
|
9623 |
+
/**
|
9624 |
+
* Abstracts the iteration of items stored in a list by leveraging the LRANGE
|
9625 |
+
* command wrapped in a fully-rewindable PHP iterator.
|
9626 |
+
*
|
9627 |
+
* This iterator tries to emulate the behaviour of cursor-based iterators based
|
9628 |
+
* on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
|
9629 |
+
* to its incremental nature with multiple fetches it can only offer limited
|
9630 |
+
* guarantees on the returned elements because the collection can change several
|
9631 |
+
* times (trimmed, deleted, overwritten) during the iteration process.
|
9632 |
+
*
|
9633 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9634 |
+
* @link http://redis.io/commands/lrange
|
9635 |
+
*/
|
9636 |
+
class ListKey implements Iterator
|
9637 |
+
{
|
9638 |
+
protected $client;
|
9639 |
+
protected $count;
|
9640 |
+
protected $key;
|
9641 |
+
|
9642 |
+
protected $valid;
|
9643 |
+
protected $fetchmore;
|
9644 |
+
protected $elements;
|
9645 |
+
protected $position;
|
9646 |
+
protected $current;
|
9647 |
+
|
9648 |
+
/**
|
9649 |
+
* @param ClientInterface $client Client connected to Redis.
|
9650 |
+
* @param string $key Redis list key.
|
9651 |
+
* @param int $count Number of items retrieved on each fetch operation.
|
9652 |
+
*
|
9653 |
+
* @throws \InvalidArgumentException
|
9654 |
+
*/
|
9655 |
+
public function __construct(ClientInterface $client, $key, $count = 10)
|
9656 |
+
{
|
9657 |
+
$this->requiredCommand($client, 'LRANGE');
|
9658 |
+
|
9659 |
+
if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
|
9660 |
+
throw new InvalidArgumentException('The $count argument must be a positive integer.');
|
9661 |
+
}
|
9662 |
+
|
9663 |
+
$this->client = $client;
|
9664 |
+
$this->key = $key;
|
9665 |
+
$this->count = $count;
|
9666 |
+
|
9667 |
+
$this->reset();
|
9668 |
+
}
|
9669 |
+
|
9670 |
+
/**
|
9671 |
+
* Ensures that the client instance supports the specified Redis command
|
9672 |
+
* required to fetch elements from the server to perform the iteration.
|
9673 |
+
*
|
9674 |
+
* @param ClientInterface $client Client connected to Redis.
|
9675 |
+
* @param string $commandID Command ID.
|
9676 |
+
*
|
9677 |
+
* @throws NotSupportedException
|
9678 |
+
*/
|
9679 |
+
protected function requiredCommand(ClientInterface $client, $commandID)
|
9680 |
+
{
|
9681 |
+
if (!$client->getProfile()->supportsCommand($commandID)) {
|
9682 |
+
throw new NotSupportedException("The current profile does not support '$commandID'.");
|
9683 |
+
}
|
9684 |
+
}
|
9685 |
+
|
9686 |
+
/**
|
9687 |
+
* Resets the inner state of the iterator.
|
9688 |
+
*/
|
9689 |
+
protected function reset()
|
9690 |
+
{
|
9691 |
+
$this->valid = true;
|
9692 |
+
$this->fetchmore = true;
|
9693 |
+
$this->elements = array();
|
9694 |
+
$this->position = -1;
|
9695 |
+
$this->current = null;
|
9696 |
+
}
|
9697 |
+
|
9698 |
+
/**
|
9699 |
+
* Fetches a new set of elements from the remote collection, effectively
|
9700 |
+
* advancing the iteration process.
|
9701 |
+
*
|
9702 |
+
* @return array
|
9703 |
+
*/
|
9704 |
+
protected function executeCommand()
|
9705 |
+
{
|
9706 |
+
return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
|
9707 |
+
}
|
9708 |
+
|
9709 |
+
/**
|
9710 |
+
* Populates the local buffer of elements fetched from the server during the
|
9711 |
+
* iteration.
|
9712 |
+
*/
|
9713 |
+
protected function fetch()
|
9714 |
+
{
|
9715 |
+
$elements = $this->executeCommand();
|
9716 |
+
|
9717 |
+
if (count($elements) < $this->count) {
|
9718 |
+
$this->fetchmore = false;
|
9719 |
+
}
|
9720 |
+
|
9721 |
+
$this->elements = $elements;
|
9722 |
+
}
|
9723 |
+
|
9724 |
+
/**
|
9725 |
+
* Extracts next values for key() and current().
|
9726 |
+
*/
|
9727 |
+
protected function extractNext()
|
9728 |
+
{
|
9729 |
+
$this->position++;
|
9730 |
+
$this->current = array_shift($this->elements);
|
9731 |
+
}
|
9732 |
+
|
9733 |
+
/**
|
9734 |
+
* {@inheritdoc}
|
9735 |
+
*/
|
9736 |
+
public function rewind()
|
9737 |
+
{
|
9738 |
+
$this->reset();
|
9739 |
+
$this->next();
|
9740 |
+
}
|
9741 |
+
|
9742 |
+
/**
|
9743 |
+
* {@inheritdoc}
|
9744 |
+
*/
|
9745 |
+
public function current()
|
9746 |
+
{
|
9747 |
+
return $this->current;
|
9748 |
+
}
|
9749 |
+
|
9750 |
+
/**
|
9751 |
+
* {@inheritdoc}
|
9752 |
+
*/
|
9753 |
+
public function key()
|
9754 |
+
{
|
9755 |
+
return $this->position;
|
9756 |
+
}
|
9757 |
+
|
9758 |
+
/**
|
9759 |
+
* {@inheritdoc}
|
9760 |
+
*/
|
9761 |
+
public function next()
|
9762 |
+
{
|
9763 |
+
if (!$this->elements && $this->fetchmore) {
|
9764 |
+
$this->fetch();
|
9765 |
+
}
|
9766 |
+
|
9767 |
+
if ($this->elements) {
|
9768 |
+
$this->extractNext();
|
9769 |
+
} else {
|
9770 |
+
$this->valid = false;
|
9771 |
+
}
|
9772 |
+
}
|
9773 |
+
|
9774 |
+
/**
|
9775 |
+
* {@inheritdoc}
|
9776 |
+
*/
|
9777 |
+
public function valid()
|
9778 |
+
{
|
9779 |
+
return $this->valid;
|
9780 |
+
}
|
9781 |
+
}
|
9782 |
+
|
9783 |
+
/* --------------------------------------------------------------------------- */
|
9784 |
+
|
9785 |
+
namespace Predis\Cluster;
|
9786 |
+
|
9787 |
+
use InvalidArgumentException;
|
9788 |
+
use Predis\Command\CommandInterface;
|
9789 |
+
use Predis\Command\ScriptCommand;
|
9790 |
+
use Predis\Cluster\Distributor\DistributorInterface;
|
9791 |
+
use Predis\Cluster\Distributor\HashRing;
|
9792 |
+
use Predis\NotSupportedException;
|
9793 |
+
use Predis\Cluster\Hash\HashGeneratorInterface;
|
9794 |
+
use Predis\Cluster\Hash\CRC16;
|
9795 |
+
|
9796 |
+
/**
|
9797 |
+
* Interface for classes defining the strategy used to calculate an hash out of
|
9798 |
+
* keys extracted from supported commands.
|
9799 |
+
*
|
9800 |
+
* This is mostly useful to support clustering via client-side sharding.
|
9801 |
+
*
|
9802 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9803 |
+
*/
|
9804 |
+
interface StrategyInterface
|
9805 |
+
{
|
9806 |
+
/**
|
9807 |
+
* Returns a slot for the given command used for clustering distribution or
|
9808 |
+
* NULL when this is not possible.
|
9809 |
+
*
|
9810 |
+
* @param CommandInterface $command Command instance.
|
9811 |
+
*
|
9812 |
+
* @return int
|
9813 |
+
*/
|
9814 |
+
public function getSlot(CommandInterface $command);
|
9815 |
+
|
9816 |
+
/**
|
9817 |
+
* Returns a slot for the given key used for clustering distribution or NULL
|
9818 |
+
* when this is not possible.
|
9819 |
+
*
|
9820 |
+
* @param string $key Key string.
|
9821 |
+
*
|
9822 |
+
* @return int
|
9823 |
+
*/
|
9824 |
+
public function getSlotByKey($key);
|
9825 |
+
|
9826 |
+
/**
|
9827 |
+
* Returns a distributor instance to be used by the cluster.
|
9828 |
+
*
|
9829 |
+
* @return DistributorInterface
|
9830 |
+
*/
|
9831 |
+
public function getDistributor();
|
9832 |
+
}
|
9833 |
+
|
9834 |
+
/**
|
9835 |
+
* Common class implementing the logic needed to support clustering strategies.
|
9836 |
+
*
|
9837 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
9838 |
+
*/
|
9839 |
+
abstract class ClusterStrategy implements StrategyInterface
|
9840 |
+
{
|
9841 |
+
protected $commands;
|
9842 |
+
|
9843 |
+
/**
|
9844 |
+
*
|
9845 |
+
*/
|
9846 |
+
public function __construct()
|
9847 |
+
{
|
9848 |
+
$this->commands = $this->getDefaultCommands();
|
9849 |
+
}
|
9850 |
+
|
9851 |
+
/**
|
9852 |
+
* Returns the default map of supported commands with their handlers.
|
9853 |
+
*
|
9854 |
+
* @return array
|
9855 |
+
*/
|
9856 |
+
protected function getDefaultCommands()
|
9857 |
+
{
|
9858 |
+
$getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
|
9859 |
+
$getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
|
9860 |
+
|
9861 |
+
return array(
|
9862 |
+
/* commands operating on the key space */
|
9863 |
+
'EXISTS' => $getKeyFromFirstArgument,
|
9864 |
+
'DEL' => $getKeyFromAllArguments,
|
9865 |
+
'TYPE' => $getKeyFromFirstArgument,
|
9866 |
+
'EXPIRE' => $getKeyFromFirstArgument,
|
9867 |
+
'EXPIREAT' => $getKeyFromFirstArgument,
|
9868 |
+
'PERSIST' => $getKeyFromFirstArgument,
|
9869 |
+
'PEXPIRE' => $getKeyFromFirstArgument,
|
9870 |
+
'PEXPIREAT' => $getKeyFromFirstArgument,
|
9871 |
+
'TTL' => $getKeyFromFirstArgument,
|
9872 |
+
'PTTL' => $getKeyFromFirstArgument,
|
9873 |
+
'SORT' => $getKeyFromFirstArgument, // TODO
|
9874 |
+
'DUMP' => $getKeyFromFirstArgument,
|
9875 |
+
'RESTORE' => $getKeyFromFirstArgument,
|
9876 |
+
|
9877 |
+
/* commands operating on string values */
|
9878 |
+
'APPEND' => $getKeyFromFirstArgument,
|
9879 |
+
'DECR' => $getKeyFromFirstArgument,
|
9880 |
+
'DECRBY' => $getKeyFromFirstArgument,
|
9881 |
+
'GET' => $getKeyFromFirstArgument,
|
9882 |
+
'GETBIT' => $getKeyFromFirstArgument,
|
9883 |
+
'MGET' => $getKeyFromAllArguments,
|
9884 |
+
'SET' => $getKeyFromFirstArgument,
|
9885 |
+
'GETRANGE' => $getKeyFromFirstArgument,
|
9886 |
+
'GETSET' => $getKeyFromFirstArgument,
|
9887 |
+
'INCR' => $getKeyFromFirstArgument,
|
9888 |
+
'INCRBY' => $getKeyFromFirstArgument,
|
9889 |
+
'INCRBYFLOAT' => $getKeyFromFirstArgument,
|
9890 |
+
'SETBIT' => $getKeyFromFirstArgument,
|
9891 |
+
'SETEX' => $getKeyFromFirstArgument,
|
9892 |
+
'MSET' => array($this, 'getKeyFromInterleavedArguments'),
|
9893 |
+
'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
|
9894 |
+
'SETNX' => $getKeyFromFirstArgument,
|
9895 |
+
'SETRANGE' => $getKeyFromFirstArgument,
|
9896 |
+
'STRLEN' => $getKeyFromFirstArgument,
|
9897 |
+
'SUBSTR' => $getKeyFromFirstArgument,
|
9898 |
+
'BITOP' => array($this, 'getKeyFromBitOp'),
|
9899 |
+
'BITCOUNT' => $getKeyFromFirstArgument,
|
9900 |
+
|
9901 |
+
/* commands operating on lists */
|
9902 |
+
'LINSERT' => $getKeyFromFirstArgument,
|
9903 |
+
'LINDEX' => $getKeyFromFirstArgument,
|
9904 |
+
'LLEN' => $getKeyFromFirstArgument,
|
9905 |
+
'LPOP' => $getKeyFromFirstArgument,
|
9906 |
+
'RPOP' => $getKeyFromFirstArgument,
|
9907 |
+
'RPOPLPUSH' => $getKeyFromAllArguments,
|
9908 |
+
'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
9909 |
+
'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
|
9910 |
+
'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
|
9911 |
+
'LPUSH' => $getKeyFromFirstArgument,
|
9912 |
+
'LPUSHX' => $getKeyFromFirstArgument,
|
9913 |
+
'RPUSH' => $getKeyFromFirstArgument,
|
9914 |
+
'RPUSHX' => $getKeyFromFirstArgument,
|
9915 |
+
'LRANGE' => $getKeyFromFirstArgument,
|
9916 |
+
'LREM' => $getKeyFromFirstArgument,
|
9917 |
+
'LSET' => $getKeyFromFirstArgument,
|
9918 |
+
'LTRIM' => $getKeyFromFirstArgument,
|
9919 |
+
|
9920 |
+
/* commands operating on sets */
|
9921 |
+
'SADD' => $getKeyFromFirstArgument,
|
9922 |
+
'SCARD' => $getKeyFromFirstArgument,
|
9923 |
+
'SDIFF' => $getKeyFromAllArguments,
|
9924 |
+
'SDIFFSTORE' => $getKeyFromAllArguments,
|
9925 |
+
'SINTER' => $getKeyFromAllArguments,
|
9926 |
+
'SINTERSTORE' => $getKeyFromAllArguments,
|
9927 |
+
'SUNION' => $getKeyFromAllArguments,
|
9928 |
+
'SUNIONSTORE' => $getKeyFromAllArguments,
|
9929 |
+
'SISMEMBER' => $getKeyFromFirstArgument,
|
9930 |
+
'SMEMBERS' => $getKeyFromFirstArgument,
|
9931 |
+
'SSCAN' => $getKeyFromFirstArgument,
|
9932 |
+
'SPOP' => $getKeyFromFirstArgument,
|
9933 |
+
'SRANDMEMBER' => $getKeyFromFirstArgument,
|
9934 |
+
'SREM' => $getKeyFromFirstArgument,
|
9935 |
+
|
9936 |
+
/* commands operating on sorted sets */
|
9937 |
+
'ZADD' => $getKeyFromFirstArgument,
|
9938 |
+
'ZCARD' => $getKeyFromFirstArgument,
|
9939 |
+
'ZCOUNT' => $getKeyFromFirstArgument,
|
9940 |
+
'ZINCRBY' => $getKeyFromFirstArgument,
|
9941 |
+
'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
9942 |
+
'ZRANGE' => $getKeyFromFirstArgument,
|
9943 |
+
'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
|
9944 |
+
'ZRANK' => $getKeyFromFirstArgument,
|
9945 |
+
'ZREM' => $getKeyFromFirstArgument,
|
9946 |
+
'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
|
9947 |
+
'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
|
9948 |
+
'ZREVRANGE' => $getKeyFromFirstArgument,
|
9949 |
+
'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
|
9950 |
+
'ZREVRANK' => $getKeyFromFirstArgument,
|
9951 |
+
'ZSCORE' => $getKeyFromFirstArgument,
|
9952 |
+
'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
|
9953 |
+
'ZSCAN' => $getKeyFromFirstArgument,
|
9954 |
+
'ZLEXCOUNT' => $getKeyFromFirstArgument,
|
9955 |
+
'ZRANGEBYLEX' => $getKeyFromFirstArgument,
|
9956 |
+
'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
|
9957 |
+
|
9958 |
+
/* commands operating on hashes */
|
9959 |
+
'HDEL' => $getKeyFromFirstArgument,
|
9960 |
+
'HEXISTS' => $getKeyFromFirstArgument,
|
9961 |
+
'HGET' => $getKeyFromFirstArgument,
|
9962 |
+
'HGETALL' => $getKeyFromFirstArgument,
|
9963 |
+
'HMGET' => $getKeyFromFirstArgument,
|
9964 |
+
'HMSET' => $getKeyFromFirstArgument,
|
9965 |
+
'HINCRBY' => $getKeyFromFirstArgument,
|
9966 |
+
'HINCRBYFLOAT' => $getKeyFromFirstArgument,
|
9967 |
+
'HKEYS' => $getKeyFromFirstArgument,
|
9968 |
+
'HLEN' => $getKeyFromFirstArgument,
|
9969 |
+
'HSET' => $getKeyFromFirstArgument,
|
9970 |
+
'HSETNX' => $getKeyFromFirstArgument,
|
9971 |
+
'HVALS' => $getKeyFromFirstArgument,
|
9972 |
+
'HSCAN' => $getKeyFromFirstArgument,
|
9973 |
+
|
9974 |
+
/* commands operating on HyperLogLog */
|
9975 |
+
'PFADD' => $getKeyFromFirstArgument,
|
9976 |
+
'PFCOUNT' => $getKeyFromAllArguments,
|
9977 |
+
'PFMERGE' => $getKeyFromAllArguments,
|
9978 |
+
|
9979 |
+
/* scripting */
|
9980 |
+
'EVAL' => array($this, 'getKeyFromScriptingCommands'),
|
9981 |
+
'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
|
9982 |
+
);
|
9983 |
+
}
|
9984 |
+
|
9985 |
+
/**
|
9986 |
+
* Returns the list of IDs for the supported commands.
|
9987 |
+
*
|
9988 |
+
* @return array
|
9989 |
+
*/
|
9990 |
+
public function getSupportedCommands()
|
9991 |
+
{
|
9992 |
+
return array_keys($this->commands);
|
9993 |
+
}
|
9994 |
+
|
9995 |
+
/**
|
9996 |
+
* Sets an handler for the specified command ID.
|
9997 |
+
*
|
9998 |
+
* The signature of the callback must have a single parameter of type
|
9999 |
+
* Predis\Command\CommandInterface.
|
10000 |
+
*
|
10001 |
+
* When the callback argument is omitted or NULL, the previously associated
|
10002 |
+
* handler for the specified command ID is removed.
|
10003 |
+
*
|
10004 |
+
* @param string $commandID Command ID.
|
10005 |
+
* @param mixed $callback A valid callable object, or NULL to unset the handler.
|
10006 |
+
*
|
10007 |
+
* @throws \InvalidArgumentException
|
10008 |
+
*/
|
10009 |
+
public function setCommandHandler($commandID, $callback = null)
|
10010 |
+
{
|
10011 |
+
$commandID = strtoupper($commandID);
|
10012 |
+
|
10013 |
+
if (!isset($callback)) {
|
10014 |
+
unset($this->commands[$commandID]);
|
10015 |
+
|
10016 |
+
return;
|
10017 |
+
}
|
10018 |
+
|
10019 |
+
if (!is_callable($callback)) {
|
10020 |
+
throw new InvalidArgumentException(
|
10021 |
+
"The argument must be a callable object or NULL."
|
10022 |
+
);
|
10023 |
+
}
|
10024 |
+
|
10025 |
+
$this->commands[$commandID] = $callback;
|
10026 |
+
}
|
10027 |
+
|
10028 |
+
/**
|
10029 |
+
* Extracts the key from the first argument of a command instance.
|
10030 |
+
*
|
10031 |
+
* @param CommandInterface $command Command instance.
|
10032 |
+
*
|
10033 |
+
* @return string
|
10034 |
+
*/
|
10035 |
+
protected function getKeyFromFirstArgument(CommandInterface $command)
|
10036 |
+
{
|
10037 |
+
return $command->getArgument(0);
|
10038 |
+
}
|
10039 |
+
|
10040 |
+
/**
|
10041 |
+
* Extracts the key from a command with multiple keys only when all keys in
|
10042 |
+
* the arguments array produce the same hash.
|
10043 |
+
*
|
10044 |
+
* @param CommandInterface $command Command instance.
|
10045 |
+
*
|
10046 |
+
* @return string|null
|
10047 |
+
*/
|
10048 |
+
protected function getKeyFromAllArguments(CommandInterface $command)
|
10049 |
+
{
|
10050 |
+
$arguments = $command->getArguments();
|
10051 |
+
|
10052 |
+
if ($this->checkSameSlotForKeys($arguments)) {
|
10053 |
+
return $arguments[0];
|
10054 |
+
}
|
10055 |
+
}
|
10056 |
+
|
10057 |
+
/**
|
10058 |
+
* Extracts the key from a command with multiple keys only when all keys in
|
10059 |
+
* the arguments array produce the same hash.
|
10060 |
+
*
|
10061 |
+
* @param CommandInterface $command Command instance.
|
10062 |
+
*
|
10063 |
+
* @return string|null
|
10064 |
+
*/
|
10065 |
+
protected function getKeyFromInterleavedArguments(CommandInterface $command)
|
10066 |
+
{
|
10067 |
+
$arguments = $command->getArguments();
|
10068 |
+
$keys = array();
|
10069 |
+
|
10070 |
+
for ($i = 0; $i < count($arguments); $i += 2) {
|
10071 |
+
$keys[] = $arguments[$i];
|
10072 |
+
}
|
10073 |
+
|
10074 |
+
if ($this->checkSameSlotForKeys($keys)) {
|
10075 |
+
return $arguments[0];
|
10076 |
+
}
|
10077 |
+
}
|
10078 |
+
|
10079 |
+
/**
|
10080 |
+
* Extracts the key from BLPOP and BRPOP commands.
|
10081 |
+
*
|
10082 |
+
* @param CommandInterface $command Command instance.
|
10083 |
+
*
|
10084 |
+
* @return string|null
|
10085 |
+
*/
|
10086 |
+
protected function getKeyFromBlockingListCommands(CommandInterface $command)
|
10087 |
+
{
|
10088 |
+
$arguments = $command->getArguments();
|
10089 |
+
|
10090 |
+
if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
|
10091 |
+
return $arguments[0];
|
10092 |
+
}
|
10093 |
+
}
|
10094 |
+
|
10095 |
+
/**
|
10096 |
+
* Extracts the key from BITOP command.
|
10097 |
+
*
|
10098 |
+
* @param CommandInterface $command Command instance.
|
10099 |
+
*
|
10100 |
+
* @return string|null
|
10101 |
+
*/
|
10102 |
+
protected function getKeyFromBitOp(CommandInterface $command)
|
10103 |
+
{
|
10104 |
+
$arguments = $command->getArguments();
|
10105 |
+
|
10106 |
+
if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
|
10107 |
+
return $arguments[1];
|
10108 |
+
}
|
10109 |
+
}
|
10110 |
+
|
10111 |
+
/**
|
10112 |
+
* Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
|
10113 |
+
*
|
10114 |
+
* @param CommandInterface $command Command instance.
|
10115 |
+
*
|
10116 |
+
* @return string|null
|
10117 |
+
*/
|
10118 |
+
protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
|
10119 |
+
{
|
10120 |
+
$arguments = $command->getArguments();
|
10121 |
+
$keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
|
10122 |
+
|
10123 |
+
if ($this->checkSameSlotForKeys($keys)) {
|
10124 |
+
return $arguments[0];
|
10125 |
+
}
|
10126 |
+
}
|
10127 |
+
|
10128 |
+
/**
|
10129 |
+
* Extracts the key from EVAL and EVALSHA commands.
|
10130 |
+
*
|
10131 |
+
* @param CommandInterface $command Command instance.
|
10132 |
+
*
|
10133 |
+
* @return string|null
|
10134 |
+
*/
|
10135 |
+
protected function getKeyFromScriptingCommands(CommandInterface $command)
|
10136 |
+
{
|
10137 |
+
if ($command instanceof ScriptCommand) {
|
10138 |
+
$keys = $command->getKeys();
|
10139 |
+
} else {
|
10140 |
+
$keys = array_slice($args = $command->getArguments(), 2, $args[1]);
|
10141 |
+
}
|
10142 |
+
|
10143 |
+
if ($keys && $this->checkSameSlotForKeys($keys)) {
|
10144 |
+
return $keys[0];
|
10145 |
+
}
|
10146 |
+
}
|
10147 |
+
|
10148 |
+
/**
|
10149 |
+
* {@inheritdoc}
|
10150 |
+
*/
|
10151 |
+
public function getSlot(CommandInterface $command)
|
10152 |
+
{
|
10153 |
+
$slot = $command->getSlot();
|
10154 |
+
|
10155 |
+
if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
|
10156 |
+
$key = call_user_func($this->commands[$cmdID], $command);
|
10157 |
+
|
10158 |
+
if (isset($key)) {
|
10159 |
+
$slot = $this->getSlotByKey($key);
|
10160 |
+
$command->setSlot($slot);
|
10161 |
+
}
|
10162 |
+
}
|
10163 |
+
|
10164 |
+
return $slot;
|
10165 |
+
}
|
10166 |
+
|
10167 |
+
/**
|
10168 |
+
* Checks if the specified array of keys will generate the same hash.
|
10169 |
+
*
|
10170 |
+
* @param array $keys Array of keys.
|
10171 |
+
*
|
10172 |
+
* @return bool
|
10173 |
+
*/
|
10174 |
+
protected function checkSameSlotForKeys(array $keys)
|
10175 |
+
{
|
10176 |
+
if (!$count = count($keys)) {
|
10177 |
+
return false;
|
10178 |
+
}
|
10179 |
+
|
10180 |
+
$currentSlot = $this->getSlotByKey($keys[0]);
|
10181 |
+
|
10182 |
+
for ($i = 1; $i < $count; $i++) {
|
10183 |
+
$nextSlot = $this->getSlotByKey($keys[$i]);
|
10184 |
+
|
10185 |
+
if ($currentSlot !== $nextSlot) {
|
10186 |
+
return false;
|
10187 |
+
}
|
10188 |
+
|
10189 |
+
$currentSlot = $nextSlot;
|
10190 |
+
}
|
10191 |
+
|
10192 |
+
return true;
|
10193 |
+
}
|
10194 |
+
|
10195 |
+
/**
|
10196 |
+
* Returns only the hashable part of a key (delimited by "{...}"), or the
|
10197 |
+
* whole key if a key tag is not found in the string.
|
10198 |
+
*
|
10199 |
+
* @param string $key A key.
|
10200 |
+
*
|
10201 |
+
* @return string
|
10202 |
+
*/
|
10203 |
+
protected function extractKeyTag($key)
|
10204 |
+
{
|
10205 |
+
if (false !== $start = strpos($key, '{')) {
|
10206 |
+
if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
|
10207 |
+
$key = substr($key, $start, $end - $start);
|
10208 |
+
}
|
10209 |
+
}
|
10210 |
+
|
10211 |
+
return $key;
|
10212 |
+
}
|
10213 |
+
}
|
10214 |
+
|
10215 |
+
/**
|
10216 |
+
* Default cluster strategy used by Predis to handle client-side sharding.
|
10217 |
+
*
|
10218 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10219 |
+
*/
|
10220 |
+
class PredisStrategy extends ClusterStrategy
|
10221 |
+
{
|
10222 |
+
protected $distributor;
|
10223 |
+
|
10224 |
+
/**
|
10225 |
+
* @param DistributorInterface $distributor Optional distributor instance.
|
10226 |
+
*/
|
10227 |
+
public function __construct(DistributorInterface $distributor = null)
|
10228 |
+
{
|
10229 |
+
parent::__construct();
|
10230 |
+
|
10231 |
+
$this->distributor = $distributor ?: new HashRing();
|
10232 |
+
}
|
10233 |
+
|
10234 |
+
/**
|
10235 |
+
* {@inheritdoc}
|
10236 |
+
*/
|
10237 |
+
public function getSlotByKey($key)
|
10238 |
+
{
|
10239 |
+
$key = $this->extractKeyTag($key);
|
10240 |
+
$hash = $this->distributor->hash($key);
|
10241 |
+
$slot = $this->distributor->getSlot($hash);
|
10242 |
+
|
10243 |
+
return $slot;
|
10244 |
+
}
|
10245 |
+
|
10246 |
+
/**
|
10247 |
+
* {@inheritdoc}
|
10248 |
+
*/
|
10249 |
+
protected function checkSameSlotForKeys(array $keys)
|
10250 |
+
{
|
10251 |
+
if (!$count = count($keys)) {
|
10252 |
+
return false;
|
10253 |
+
}
|
10254 |
+
|
10255 |
+
$currentKey = $this->extractKeyTag($keys[0]);
|
10256 |
+
|
10257 |
+
for ($i = 1; $i < $count; $i++) {
|
10258 |
+
$nextKey = $this->extractKeyTag($keys[$i]);
|
10259 |
+
|
10260 |
+
if ($currentKey !== $nextKey) {
|
10261 |
+
return false;
|
10262 |
+
}
|
10263 |
+
|
10264 |
+
$currentKey = $nextKey;
|
10265 |
+
}
|
10266 |
+
|
10267 |
+
return true;
|
10268 |
+
}
|
10269 |
+
|
10270 |
+
/**
|
10271 |
+
* {@inheritdoc}
|
10272 |
+
*/
|
10273 |
+
public function getDistributor()
|
10274 |
+
{
|
10275 |
+
return $this->distributor;
|
10276 |
+
}
|
10277 |
+
}
|
10278 |
+
|
10279 |
+
/**
|
10280 |
+
* Default class used by Predis to calculate hashes out of keys of
|
10281 |
+
* commands supported by redis-cluster.
|
10282 |
+
*
|
10283 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10284 |
+
*/
|
10285 |
+
class RedisStrategy extends ClusterStrategy
|
10286 |
+
{
|
10287 |
+
protected $hashGenerator;
|
10288 |
+
|
10289 |
+
/**
|
10290 |
+
* @param HashGeneratorInterface $hashGenerator Hash generator instance.
|
10291 |
+
*/
|
10292 |
+
public function __construct(HashGeneratorInterface $hashGenerator = null)
|
10293 |
+
{
|
10294 |
+
parent::__construct();
|
10295 |
+
|
10296 |
+
$this->hashGenerator = $hashGenerator ?: new CRC16();
|
10297 |
+
}
|
10298 |
+
|
10299 |
+
/**
|
10300 |
+
* {@inheritdoc}
|
10301 |
+
*/
|
10302 |
+
public function getSlotByKey($key)
|
10303 |
+
{
|
10304 |
+
$key = $this->extractKeyTag($key);
|
10305 |
+
$slot = $this->hashGenerator->hash($key) & 0x3FFF;
|
10306 |
+
|
10307 |
+
return $slot;
|
10308 |
+
}
|
10309 |
+
|
10310 |
+
/**
|
10311 |
+
* {@inheritdoc}
|
10312 |
+
*/
|
10313 |
+
public function getDistributor()
|
10314 |
+
{
|
10315 |
+
throw new NotSupportedException(
|
10316 |
+
'This cluster strategy does not provide an external distributor'
|
10317 |
+
);
|
10318 |
+
}
|
10319 |
+
}
|
10320 |
+
|
10321 |
+
/* --------------------------------------------------------------------------- */
|
10322 |
+
|
10323 |
+
namespace Predis\Protocol;
|
10324 |
+
|
10325 |
+
use Predis\CommunicationException;
|
10326 |
+
use Predis\Command\CommandInterface;
|
10327 |
+
use Predis\Connection\CompositeConnectionInterface;
|
10328 |
+
|
10329 |
+
/**
|
10330 |
+
* Defines a pluggable protocol processor capable of serializing commands and
|
10331 |
+
* deserializing responses into PHP objects directly from a connection.
|
10332 |
+
*
|
10333 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10334 |
+
*/
|
10335 |
+
interface ProtocolProcessorInterface
|
10336 |
+
{
|
10337 |
+
/**
|
10338 |
+
* Writes a request over a connection to Redis.
|
10339 |
+
*
|
10340 |
+
* @param CompositeConnectionInterface $connection Redis connection.
|
10341 |
+
* @param CommandInterface $command Command instance.
|
10342 |
+
*/
|
10343 |
+
public function write(CompositeConnectionInterface $connection, CommandInterface $command);
|
10344 |
+
|
10345 |
+
/**
|
10346 |
+
* Reads a response from a connection to Redis.
|
10347 |
+
*
|
10348 |
+
* @param CompositeConnectionInterface $connection Redis connection.
|
10349 |
+
*
|
10350 |
+
* @return mixed
|
10351 |
+
*/
|
10352 |
+
public function read(CompositeConnectionInterface $connection);
|
10353 |
+
}
|
10354 |
+
|
10355 |
+
/**
|
10356 |
+
* Defines a pluggable reader capable of parsing responses returned by Redis and
|
10357 |
+
* deserializing them to PHP objects.
|
10358 |
+
*
|
10359 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10360 |
+
*/
|
10361 |
+
interface ResponseReaderInterface
|
10362 |
+
{
|
10363 |
+
/**
|
10364 |
+
* Reads a response from a connection to Redis.
|
10365 |
+
*
|
10366 |
+
* @param CompositeConnectionInterface $connection Redis connection.
|
10367 |
+
*
|
10368 |
+
* @return mixed
|
10369 |
+
*/
|
10370 |
+
public function read(CompositeConnectionInterface $connection);
|
10371 |
+
}
|
10372 |
+
|
10373 |
+
/**
|
10374 |
+
* Defines a pluggable serializer for Redis commands.
|
10375 |
+
*
|
10376 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10377 |
+
*/
|
10378 |
+
interface RequestSerializerInterface
|
10379 |
+
{
|
10380 |
+
/**
|
10381 |
+
* Serializes a Redis command.
|
10382 |
+
*
|
10383 |
+
* @param CommandInterface $command Redis command.
|
10384 |
+
*
|
10385 |
+
* @return string
|
10386 |
+
*/
|
10387 |
+
public function serialize(CommandInterface $command);
|
10388 |
+
}
|
10389 |
+
|
10390 |
+
/**
|
10391 |
+
* Exception used to indentify errors encountered while parsing the Redis wire
|
10392 |
+
* protocol.
|
10393 |
+
*
|
10394 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10395 |
+
*/
|
10396 |
+
class ProtocolException extends CommunicationException
|
10397 |
+
{
|
10398 |
+
}
|
10399 |
+
|
10400 |
+
/* --------------------------------------------------------------------------- */
|
10401 |
+
|
10402 |
+
namespace Predis\Connection\Aggregate;
|
10403 |
+
|
10404 |
+
use Predis\Connection\AggregateConnectionInterface;
|
10405 |
+
use InvalidArgumentException;
|
10406 |
+
use RuntimeException;
|
10407 |
+
use Predis\Command\CommandInterface;
|
10408 |
+
use Predis\Connection\NodeConnectionInterface;
|
10409 |
+
use Predis\Replication\ReplicationStrategy;
|
10410 |
+
use ArrayIterator;
|
10411 |
+
use Countable;
|
10412 |
+
use IteratorAggregate;
|
10413 |
+
use Predis\NotSupportedException;
|
10414 |
+
use Predis\Cluster\PredisStrategy;
|
10415 |
+
use Predis\Cluster\StrategyInterface;
|
10416 |
+
use OutOfBoundsException;
|
10417 |
+
use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
|
10418 |
+
use Predis\Command\RawCommand;
|
10419 |
+
use Predis\Connection\FactoryInterface;
|
10420 |
+
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
10421 |
+
|
10422 |
+
/**
|
10423 |
+
* Defines a cluster of Redis servers formed by aggregating multiple connection
|
10424 |
+
* instances to single Redis nodes.
|
10425 |
+
*
|
10426 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10427 |
+
*/
|
10428 |
+
interface ClusterInterface extends AggregateConnectionInterface
|
10429 |
+
{
|
10430 |
+
}
|
10431 |
+
|
10432 |
+
/**
|
10433 |
+
* Defines a group of Redis nodes in a master / slave replication setup.
|
10434 |
+
*
|
10435 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10436 |
+
*/
|
10437 |
+
interface ReplicationInterface extends AggregateConnectionInterface
|
10438 |
+
{
|
10439 |
+
/**
|
10440 |
+
* Switches the internal connection instance in use.
|
10441 |
+
*
|
10442 |
+
* @param string $connection Alias of a connection
|
10443 |
+
*/
|
10444 |
+
public function switchTo($connection);
|
10445 |
+
|
10446 |
+
/**
|
10447 |
+
* Returns the connection instance currently in use by the aggregate
|
10448 |
+
* connection.
|
10449 |
+
*
|
10450 |
+
* @return NodeConnectionInterface
|
10451 |
+
*/
|
10452 |
+
public function getCurrent();
|
10453 |
+
|
10454 |
+
/**
|
10455 |
+
* Returns the connection instance for the master Redis node.
|
10456 |
+
*
|
10457 |
+
* @return NodeConnectionInterface
|
10458 |
+
*/
|
10459 |
+
public function getMaster();
|
10460 |
+
|
10461 |
+
/**
|
10462 |
+
* Returns a list of connection instances to slave nodes.
|
10463 |
+
*
|
10464 |
+
* @return NodeConnectionInterface
|
10465 |
+
*/
|
10466 |
+
public function getSlaves();
|
10467 |
+
}
|
10468 |
+
|
10469 |
+
/**
|
10470 |
+
* Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
|
10471 |
+
*
|
10472 |
+
* This connection backend offers smart support for redis-cluster by handling
|
10473 |
+
* automatic slots map (re)generation upon -MOVED or -ASK responses returned by
|
10474 |
+
* Redis when redirecting a client to a different node.
|
10475 |
+
*
|
10476 |
+
* The cluster can be pre-initialized using only a subset of the actual nodes in
|
10477 |
+
* the cluster, Predis will do the rest by adjusting the slots map and creating
|
10478 |
+
* the missing underlying connection instances on the fly.
|
10479 |
+
*
|
10480 |
+
* It is possible to pre-associate connections to a slots range with the "slots"
|
10481 |
+
* parameter in the form "$first-$last". This can greatly reduce runtime node
|
10482 |
+
* guessing and redirections.
|
10483 |
+
*
|
10484 |
+
* It is also possible to ask for the full and updated slots map directly to one
|
10485 |
+
* of the nodes and optionally enable such a behaviour upon -MOVED redirections.
|
10486 |
+
* Asking for the cluster configuration to Redis is actually done by issuing a
|
10487 |
+
* CLUSTER SLOTS command to a random node in the pool.
|
10488 |
+
*
|
10489 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
10490 |
+
*/
|
10491 |
+
class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
|
10492 |
+
{
|
10493 |
+
private $useClusterSlots = true;
|
10494 |
+
private $defaultParameters = array();
|
10495 |
+
private $pool = array();
|
10496 |
+
private $slots = array();
|
10497 |
+
private $slotsMap;
|
10498 |
+
private $strategy;
|
10499 |
+
private $connections;
|
10500 |
+
|
10501 |
+
/**
|
10502 |
+
* @param FactoryInterface $connections Optional connection factory.
|
10503 |
+
* @param StrategyInterface $strategy Optional cluster strategy.
|
10504 |
+
*/
|
10505 |
+
public function __construct(
|
10506 |
+
FactoryInterface $connections,
|
10507 |
+
StrategyInterface $strategy = null
|
10508 |
+
) {
|
10509 |
+
$this->connections = $connections;
|
10510 |
+
$this->strategy = $strategy ?: new RedisClusterStrategy();
|
10511 |
+
}
|
10512 |
+
|
10513 |
+
/**
|
10514 |
+
* {@inheritdoc}
|
10515 |
+
*/
|
10516 |
+
public function isConnected()
|
10517 |
+
{
|
10518 |
+
foreach ($this->pool as $connection) {
|
10519 |
+
if ($connection->isConnected()) {
|
10520 |
+
return true;
|
10521 |
+
}
|
10522 |
+
}
|
10523 |
+
|
10524 |
+
return false;
|
10525 |
+
}
|
10526 |
+
|
10527 |
+
/**
|
10528 |
+
* {@inheritdoc}
|
10529 |
+
*/
|
10530 |
+
public function connect()
|
10531 |
+
{
|
10532 |
+
if ($connection = $this->getRandomConnection()) {
|
10533 |
+
$connection->connect();
|
10534 |
+
}
|
10535 |
+
}
|
10536 |
+
|
10537 |
+
/**
|
10538 |
+
* {@inheritdoc}
|
10539 |
+
*/
|
10540 |
+
public function disconnect()
|
10541 |
+
{
|
10542 |
+
foreach ($this->pool as $connection) {
|
10543 |
+
$connection->disconnect();
|
10544 |
+
}
|
10545 |
+
}
|
10546 |
+
|
10547 |
+
/**
|
10548 |
+
* {@inheritdoc}
|
10549 |
+
*/
|
10550 |
+
public function add(NodeConnectionInterface $connection)
|
10551 |
+
{
|
10552 |
+
$this->pool[(string) $connection] = $connection;
|
10553 |
+
unset($this->slotsMap);
|
10554 |
+
}
|
10555 |
+
|
10556 |
+
/**
|
10557 |
+
* {@inheritdoc}
|
10558 |
+
*/
|
10559 |
+
public function remove(NodeConnectionInterface $connection)
|
10560 |
+
{
|
10561 |
+
if (false !== $id = array_search($connection, $this->pool, true)) {
|
10562 |
+
unset(
|
10563 |
+
$this->pool[$id],
|
10564 |
+
$this->slotsMap
|
10565 |
+
);
|
10566 |
+
|
10567 |
+
return true;
|
10568 |
+
}
|
10569 |
+
|
10570 |
+
return false;
|
10571 |
+
}
|
10572 |
+
|
10573 |
+
/**
|
10574 |
+
* Removes a connection instance by using its identifier.
|
10575 |
+
*
|
10576 |
+
* @param string $connectionID Connection identifier.
|
10577 |
+
*
|
10578 |
+
* @return bool True if the connection was in the pool.
|
10579 |
+
*/
|
10580 |
+
public function removeById($connectionID)
|
10581 |
+
{
|
10582 |
+
if (isset($this->pool[$connectionID])) {
|
10583 |
+
unset(
|
10584 |
+
$this->pool[$connectionID],
|
10585 |
+
$this->slotsMap
|
10586 |
+
);
|
10587 |
+
|
10588 |
+
return true;
|
10589 |
+
}
|
10590 |
+
|
10591 |
+
return false;
|
10592 |
+
}
|
10593 |
+
|
10594 |
+
/**
|
10595 |
+
* Generates the current slots map by guessing the cluster configuration out
|
10596 |
+
* of the connection parameters of the connections in the pool.
|
10597 |
+
*
|
10598 |
+
* Generation is based on the same algorithm used by Redis to generate the
|
10599 |
+
* cluster, so it is most effective when all of the connections supplied on
|
10600 |
+
* initialization have the "slots" parameter properly set accordingly to the
|
10601 |
+
* current cluster configuration.
|
10602 |
+
*/
|
10603 |
+
public function buildSlotsMap()
|
10604 |
+
{
|
10605 |
+
$this->slotsMap = array();
|
10606 |
+
|
10607 |
+
foreach ($this->pool as $connectionID => $connection) {
|
10608 |
+
$parameters = $connection->getParameters();
|
10609 |
+
|
10610 |
+
if (!isset($parameters->slots)) {
|
10611 |
+
continue;
|
10612 |
+
}
|
10613 |
+
|
10614 |
+
$slots = explode('-', $parameters->slots, 2);
|
10615 |
+
$this->setSlots($slots[0], $slots[1], $connectionID);
|
10616 |
+
}
|
10617 |
+
}
|
10618 |
+
|
10619 |
+
/**
|
10620 |
+
* Generates an updated slots map fetching the cluster configuration using
|
10621 |
+
* the CLUSTER SLOTS command against the specified node or a random one from
|
10622 |
+
* the pool.
|
10623 |
+
*
|
10624 |
+
* @param NodeConnectionInterface $connection Optional connection instance.
|
10625 |
+
*
|
10626 |
+
* @return array
|
10627 |
+
*/
|
10628 |
+
public function askSlotsMap(NodeConnectionInterface $connection = null)
|
10629 |
+
{
|
10630 |
+
if (!$connection && !$connection = $this->getRandomConnection()) {
|
10631 |
+
return array();
|
10632 |
+
}
|
10633 |
+
$command = RawCommand::create('CLUSTER', 'SLOTS');
|
10634 |
+
$response = $connection->executeCommand($command);
|
10635 |
+
|
10636 |
+
foreach ($response as $slots) {
|
10637 |
+
// We only support master servers for now, so we ignore subsequent
|
10638 |
+
// elements in the $slots array identifying slaves.
|
10639 |
+
list($start, $end, $master) = $slots;
|
10640 |
+
|
10641 |
+
if ($master[0] === '') {
|
10642 |
+
$this->setSlots($start, $end, (string) $connection);
|
10643 |
+
} else {
|
10644 |
+
$this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
|
10645 |
+
}
|
10646 |
+
}
|
10647 |
+
|
10648 |
+
return $this->slotsMap;
|
10649 |
+
}
|
10650 |
+
|
10651 |
+
/**
|
10652 |
+
* Returns the current slots map for the cluster.
|
10653 |
+
*
|
10654 |
+
* @return array
|
10655 |
+
*/
|
10656 |
+
public function getSlotsMap()
|
10657 |
+
{
|
10658 |
+
if (!isset($this->slotsMap)) {
|
10659 |
+
$this->slotsMap = array();
|
10660 |
+
}
|
10661 |
+
|
10662 |
+
return $this->slotsMap;
|
10663 |
+
}
|
10664 |
+
|
10665 |
+
/**
|
10666 |
+
* Pre-associates a connection to a slots range to avoid runtime guessing.
|
10667 |
+
*
|
10668 |
+
* @param int $first Initial slot of the range.
|
10669 |
+
* @param int $last Last slot of the range.
|
10670 |
+
* @param NodeConnectionInterface|string $connection ID or connection instance.
|
10671 |
+
*
|
10672 |
+
* @throws \OutOfBoundsException
|
10673 |
+
*/
|
10674 |
+
public function setSlots($first, $last, $connection)
|
10675 |
+
{
|
10676 |
+
if ($first < 0x0000 || $first > 0x3FFF ||
|
10677 |
+
$last < 0x0000 || $last > 0x3FFF ||
|
10678 |
+
$last < $first
|
10679 |
+
) {
|
10680 |
+
throw new OutOfBoundsException(
|
10681 |
+
"Invalid slot range for $connection: [$first-$last]."
|
10682 |
+
);
|
10683 |
+
}
|
10684 |
+
|
10685 |
+
$slots = array_fill($first, $last - $first + 1, (string) $connection);
|
10686 |
+
$this->slotsMap = $this->getSlotsMap() + $slots;
|
10687 |
+
}
|
10688 |
+
|
10689 |
+
/**
|
10690 |
+
* Guesses the correct node associated to a given slot using a precalculated
|
10691 |
+
* slots map, falling back to the same logic used by Redis to initialize a
|
10692 |
+
* cluster (best-effort).
|
10693 |
+
*
|
10694 |
+
* @param int $slot Slot index.
|
10695 |
+
*
|
10696 |
+
* @return string Connection ID.
|
10697 |
+
*/
|
10698 |
+
protected function guessNode($slot)
|
10699 |
+
{
|
10700 |
+
if (!isset($this->slotsMap)) {
|
10701 |
+
$this->buildSlotsMap();
|
10702 |
+
}
|
10703 |
+
|
10704 |
+
if (isset($this->slotsMap[$slot])) {
|
10705 |
+
return $this->slotsMap[$slot];
|
10706 |
+
}
|
10707 |
+
|
10708 |
+
$count = count($this->pool);
|
10709 |
+
$index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
|
10710 |
+
$nodes = array_keys($this->pool);
|
10711 |
+
|
10712 |
+
return $nodes[$index];
|
10713 |
+
}
|
10714 |
+
|
10715 |
+
/**
|
10716 |
+
* Creates a new connection instance from the given connection ID.
|
10717 |
+
*
|
10718 |
+
* @param string $connectionID Identifier for the connection.
|
10719 |
+
*
|
10720 |
+
* @return NodeConnectionInterface
|
10721 |
+
*/
|
10722 |
+
protected function createConnection($connectionID)
|
10723 |
+
{
|
10724 |
+
$host = explode(':', $connectionID, 2);
|
10725 |
+
|
10726 |
+
$parameters = array_merge($this->defaultParameters, array(
|
10727 |
+
'host' => $host[0],
|
10728 |
+
'port' => $host[1],
|
10729 |
+
));
|
10730 |
+
|
10731 |
+
$connection = $this->connections->create($parameters);
|
10732 |
+
|
10733 |
+
return $connection;
|
10734 |
+
}
|
10735 |
+
|
10736 |
+
/**
|
10737 |
+
* {@inheritdoc}
|
10738 |
+
*/
|
10739 |
+
public function getConnection(CommandInterface $command)
|
10740 |
+
{
|
10741 |
+
$slot = $this->strategy->getSlot($command);
|
10742 |
+
|
10743 |
+
if (!isset($slot)) {
|
10744 |
+
throw new NotSupportedException(
|
10745 |
+
"Cannot use '{$command->getId()}' with redis-cluster."
|
10746 |
+
);
|
10747 |
+
}
|
10748 |
+
|
10749 |
+
if (isset($this->slots[$slot])) {
|
10750 |
+
return $this->slots[$slot];
|
10751 |
+
} else {
|
10752 |
+
return $this->getConnectionBySlot($slot);
|
10753 |
+
}
|
10754 |
+
}
|
10755 |
+
|
10756 |
+
/**
|
10757 |
+
* Returns the connection currently associated to a given slot.
|
10758 |
+
*
|
10759 |
+
* @param int $slot Slot index.
|
10760 |
+
*
|
10761 |
+
* @return NodeConnectionInterface
|
10762 |
+
*
|
10763 |
+
* @throws \OutOfBoundsException
|
10764 |
+
*/
|
10765 |
+
public function getConnectionBySlot($slot)
|
10766 |
+
{
|
10767 |
+
if ($slot < 0x0000 || $slot > 0x3FFF) {
|
10768 |
+
throw new OutOfBoundsException("Invalid slot [$slot].");
|
10769 |
+
}
|
10770 |
+
|
10771 |
+
if (isset($this->slots[$slot])) {
|
10772 |
+
return $this->slots[$slot];
|
10773 |
+
}
|
10774 |
+
|
10775 |
+
$connectionID = $this->guessNode($slot);
|
10776 |
+
|
10777 |
+
if (!$connection = $this->getConnectionById($connectionID)) {
|
10778 |
+
$connection = $this->createConnection($connectionID);
|
10779 |
+
$this->pool[$connectionID] = $connection;
|
10780 |
+
}
|
10781 |
+
|
10782 |
+
return $this->slots[$slot] = $connection;
|
10783 |
+
}
|
10784 |
+
|
10785 |
+
/**
|
10786 |
+
* {@inheritdoc}
|
10787 |
+
*/
|
10788 |
+
public function getConnectionById($connectionID)
|
10789 |
+
{
|
10790 |
+
if (isset($this->pool[$connectionID])) {
|
10791 |
+
return $this->pool[$connectionID];
|
10792 |
+
}
|
10793 |
+
}
|
10794 |
+
|
10795 |
+
/**
|
10796 |
+
* Returns a random connection from the pool.
|
10797 |
+
*
|
10798 |
+
* @return NodeConnectionInterface|null
|
10799 |
+
*/
|
10800 |
+
protected function getRandomConnection()
|
10801 |
+
{
|
10802 |
+
if ($this->pool) {
|
10803 |
+
return $this->pool[array_rand($this->pool)];
|
10804 |
+
}
|
10805 |
+
}
|
10806 |
+
|
10807 |
+
/**
|
10808 |
+
* Permanently associates the connection instance to a new slot.
|
10809 |
+
* The connection is added to the connections pool if not yet included.
|
10810 |
+
*
|
10811 |
+
* @param NodeConnectionInterface $connection Connection instance.
|
10812 |
+
* @param int $slot Target slot index.
|
10813 |
+
*/
|
10814 |
+
protected function move(NodeConnectionInterface $connection, $slot)
|
10815 |
+
{
|
10816 |
+
$this->pool[(string) $connection] = $connection;
|
10817 |
+
$this->slots[(int) $slot] = $connection;
|
10818 |
+
}
|
10819 |
+
|
10820 |
+
/**
|
10821 |
+
* Handles -ERR responses returned by Redis.
|
10822 |
+
*
|
10823 |
+
* @param CommandInterface $command Command that generated the -ERR response.
|
10824 |
+
* @param ErrorResponseInterface $error Redis error response object.
|
10825 |
+
*
|
10826 |
+
* @return mixed
|
10827 |
+
*/
|
10828 |
+
protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error)
|
10829 |
+
{
|
10830 |
+
$details = explode(' ', $error->getMessage(), 2);
|
10831 |
+
|
10832 |
+
switch ($details[0]) {
|
10833 |
+
case 'MOVED':
|
10834 |
+
return $this->onMovedResponse($command, $details[1]);
|
10835 |
+
|
10836 |
+
case 'ASK':
|
10837 |
+
return $this->onAskResponse($command, $details[1]);
|
10838 |
+
|
10839 |
+
default:
|
10840 |
+
return $error;
|
10841 |
+
}
|
10842 |
+
}
|
10843 |
+
|
10844 |
+
/**
|
10845 |
+
* Handles -MOVED responses by executing again the command against the node
|
10846 |
+
* indicated by the Redis response.
|
10847 |
+
*
|
10848 |
+
* @param CommandInterface $command Command that generated the -MOVED response.
|
10849 |
+
* @param string $details Parameters of the -MOVED response.
|
10850 |
+
*
|
10851 |
+
* @return mixed
|
10852 |
+
*/
|
10853 |
+
protected function onMovedResponse(CommandInterface $command, $details)
|
10854 |
+
{
|
10855 |
+
list($slot, $connectionID) = explode(' ', $details, 2);
|
10856 |
+
|
10857 |
+
if (!$connection = $this->getConnectionById($connectionID)) {
|
10858 |
+
$connection = $this->createConnection($connectionID);
|
10859 |
+
}
|
10860 |
+
|
10861 |
+
if ($this->useClusterSlots) {
|
10862 |
+
$this->askSlotsMap($connection);
|
10863 |
+
}
|
10864 |
+
|
10865 |
+
$this->move($connection, $slot);
|
10866 |
+
$response = $this->executeCommand($command);
|
10867 |
+
|
10868 |
+
return $response;
|
10869 |
+
}
|
10870 |
+
|
10871 |
+
/**
|
10872 |
+
* Handles -ASK responses by executing again the command against the node
|
10873 |
+
* indicated by the Redis response.
|
10874 |
+
*
|
10875 |
+
* @param CommandInterface $command Command that generated the -ASK response.
|
10876 |
+
* @param string $details Parameters of the -ASK response.
|
10877 |
+
* @return mixed
|
10878 |
+
*/
|
10879 |
+
protected function onAskResponse(CommandInterface $command, $details)
|
10880 |
+
{
|
10881 |
+
list($slot, $connectionID) = explode(' ', $details, 2);
|
10882 |
+
|
10883 |
+
if (!$connection = $this->getConnectionById($connectionID)) {
|
10884 |
+
$connection = $this->createConnection($connectionID);
|
10885 |
+
}
|
10886 |
+
$connection->executeCommand(RawCommand::create('ASKING'));
|
10887 |
+
$response = $connection->executeCommand($command);
|
10888 |
+
|
10889 |
+
return $response;
|
10890 |
+
}
|
10891 |
+
|
10892 |
+
/**
|
10893 |
+
* {@inheritdoc}
|
10894 |
+
*/
|
10895 |
+
public function writeRequest(CommandInterface $command)
|
10896 |
+
{
|
10897 |
+
$this->getConnection($command)->writeRequest($command);
|
10898 |
+
}
|
10899 |
+
|
10900 |
+
/**
|
10901 |
+
* {@inheritdoc}
|
10902 |
+
*/
|
10903 |
+
public function readResponse(CommandInterface $command)
|
10904 |
+
{
|
10905 |
+
return $this->getConnection($command)->readResponse($command);
|
10906 |
+
}
|
10907 |
+
|
10908 |
+
/**
|
10909 |
+
* {@inheritdoc}
|
10910 |
+
*/
|
10911 |
+
public function executeCommand(CommandInterface $command)
|
10912 |
+
{
|
10913 |
+
$connection = $this->getConnection($command);
|
10914 |
+
$response = $connection->executeCommand($command);
|
10915 |
+
|
10916 |
+
if ($response instanceof ErrorResponseInterface) {
|
10917 |
+
return $this->onErrorResponse($command, $response);
|
10918 |
+
}
|
10919 |
+
|
10920 |
+
return $response;
|
10921 |
+
}
|
10922 |
+
|
10923 |
+
/**
|
10924 |
+
* {@inheritdoc}
|
10925 |
+
*/
|
10926 |
+
public function count()
|
10927 |
+
{
|
10928 |
+
return count($this->pool);
|
10929 |
+
}
|
10930 |
+
|
10931 |
+
/**
|
10932 |
+
* {@inheritdoc}
|
10933 |
+
*/
|
10934 |
+
public function getIterator()
|
10935 |
+
{
|
10936 |
+
return new ArrayIterator(array_values($this->pool));
|
10937 |
+
}
|
10938 |
+
|
10939 |
+
/**
|
10940 |
+
* Returns the underlying command hash strategy used to hash commands by
|
10941 |
+
* using keys found in their arguments.
|
10942 |
+
*
|
10943 |
+
* @return StrategyInterface
|
10944 |
+
*/
|
10945 |
+
public function getClusterStrategy()
|
10946 |
+
{
|
10947 |
+
return $this->strategy;
|
10948 |
+
}
|
10949 |
+
|
10950 |
+
/**
|
10951 |
+
* Returns the underlying connection factory used to create new connection
|
10952 |
+
* instances to Redis nodes indicated by redis-cluster.
|
10953 |
+
*
|
10954 |
+
* @return FactoryInterface
|
10955 |
+
*/
|
10956 |
+
public function getConnectionFactory()
|
10957 |
+
{
|
10958 |
+
return $this->connections;
|
10959 |
+
}
|
10960 |
+
|
10961 |
+
/**
|
10962 |
+
* Enables automatic fetching of the current slots map from one of the nodes
|
10963 |
+
* using the CLUSTER SLOTS command. This option is disabled by default but
|
10964 |
+
* asking the current slots map to Redis upon -MOVED responses may reduce
|
10965 |
+
* overhead by eliminating the trial-and-error nature of the node guessing
|
10966 |
+
* procedure, mostly when targeting many keys that would end up in a lot of
|
10967 |
+
* redirections.
|
10968 |
+
*
|
10969 |
+
* The slots map can still be manually fetched using the askSlotsMap()
|
10970 |
+
* method whether or not this option is enabled.
|
10971 |
+
*
|
10972 |
+
* @param bool $value Enable or disable the use of CLUSTER SLOTS.
|
10973 |
+
*/
|
10974 |
+
public function useClusterSlots($value)
|
10975 |
+
{
|
10976 |
+
$this->useClusterSlots = (bool) $value;
|
10977 |
+
}
|
10978 |
+
|
10979 |
+
/**
|
10980 |
+
* Sets a default array of connection parameters to be applied when creating
|
10981 |
+
* new connection instances on the fly when they are not part of the initial
|
10982 |
+
* pool supplied upon cluster initialization.
|
10983 |
+
*
|
10984 |
+
* These parameters are not applied to connections added to the pool using
|
10985 |
+
* the add() method.
|
10986 |
+
*
|
10987 |
+
* @param array $parameters Array of connection parameters.
|
10988 |
+
*/
|
10989 |
+
public function setDefaultParameters(array $parameters)
|
10990 |
+
{
|
10991 |
+
$this->defaultParameters = array_merge(
|
10992 |
+
$this->defaultParameters,
|
10993 |
+
$parameters ?: array()
|
10994 |
+
);
|
10995 |
+
}
|
10996 |
+
}
|
10997 |
+
|
10998 |
+
/**
|
10999 |
+
* Abstraction for a cluster of aggregate connections to various Redis servers
|
11000 |
+
* implementing client-side sharding based on pluggable distribution strategies.
|
11001 |
+
*
|
11002 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11003 |
+
* @todo Add the ability to remove connections from pool.
|
11004 |
+
*/
|
11005 |
+
class PredisCluster implements ClusterInterface, IteratorAggregate, Countable
|
11006 |
+
{
|
11007 |
+
private $pool;
|
11008 |
+
private $strategy;
|
11009 |
+
private $distributor;
|
11010 |
+
|
11011 |
+
/**
|
11012 |
+
* @param StrategyInterface $strategy Optional cluster strategy.
|
11013 |
+
*/
|
11014 |
+
public function __construct(StrategyInterface $strategy = null)
|
11015 |
+
{
|
11016 |
+
$this->pool = array();
|
11017 |
+
$this->strategy = $strategy ?: new PredisStrategy();
|
11018 |
+
$this->distributor = $this->strategy->getDistributor();
|
11019 |
+
}
|
11020 |
+
|
11021 |
+
/**
|
11022 |
+
* {@inheritdoc}
|
11023 |
+
*/
|
11024 |
+
public function isConnected()
|
11025 |
+
{
|
11026 |
+
foreach ($this->pool as $connection) {
|
11027 |
+
if ($connection->isConnected()) {
|
11028 |
+
return true;
|
11029 |
+
}
|
11030 |
+
}
|
11031 |
+
|
11032 |
+
return false;
|
11033 |
+
}
|
11034 |
+
|
11035 |
+
/**
|
11036 |
+
* {@inheritdoc}
|
11037 |
+
*/
|
11038 |
+
public function connect()
|
11039 |
+
{
|
11040 |
+
foreach ($this->pool as $connection) {
|
11041 |
+
$connection->connect();
|
11042 |
+
}
|
11043 |
+
}
|
11044 |
+
|
11045 |
+
/**
|
11046 |
+
* {@inheritdoc}
|
11047 |
+
*/
|
11048 |
+
public function disconnect()
|
11049 |
+
{
|
11050 |
+
foreach ($this->pool as $connection) {
|
11051 |
+
$connection->disconnect();
|
11052 |
+
}
|
11053 |
+
}
|
11054 |
+
|
11055 |
+
/**
|
11056 |
+
* {@inheritdoc}
|
11057 |
+
*/
|
11058 |
+
public function add(NodeConnectionInterface $connection)
|
11059 |
+
{
|
11060 |
+
$parameters = $connection->getParameters();
|
11061 |
+
|
11062 |
+
if (isset($parameters->alias)) {
|
11063 |
+
$this->pool[$parameters->alias] = $connection;
|
11064 |
+
} else {
|
11065 |
+
$this->pool[] = $connection;
|
11066 |
+
}
|
11067 |
+
|
11068 |
+
$weight = isset($parameters->weight) ? $parameters->weight : null;
|
11069 |
+
$this->distributor->add($connection, $weight);
|
11070 |
+
}
|
11071 |
+
|
11072 |
+
/**
|
11073 |
+
* {@inheritdoc}
|
11074 |
+
*/
|
11075 |
+
public function remove(NodeConnectionInterface $connection)
|
11076 |
+
{
|
11077 |
+
if (($id = array_search($connection, $this->pool, true)) !== false) {
|
11078 |
+
unset($this->pool[$id]);
|
11079 |
+
$this->distributor->remove($connection);
|
11080 |
+
|
11081 |
+
return true;
|
11082 |
+
}
|
11083 |
+
|
11084 |
+
return false;
|
11085 |
+
}
|
11086 |
+
|
11087 |
+
/**
|
11088 |
+
* Removes a connection instance using its alias or index.
|
11089 |
+
*
|
11090 |
+
* @param string $connectionID Alias or index of a connection.
|
11091 |
+
*
|
11092 |
+
* @return bool Returns true if the connection was in the pool.
|
11093 |
+
*/
|
11094 |
+
public function removeById($connectionID)
|
11095 |
+
{
|
11096 |
+
if ($connection = $this->getConnectionById($connectionID)) {
|
11097 |
+
return $this->remove($connection);
|
11098 |
+
}
|
11099 |
+
|
11100 |
+
return false;
|
11101 |
+
}
|
11102 |
+
|
11103 |
+
/**
|
11104 |
+
* {@inheritdoc}
|
11105 |
+
*/
|
11106 |
+
public function getConnection(CommandInterface $command)
|
11107 |
+
{
|
11108 |
+
$slot = $this->strategy->getSlot($command);
|
11109 |
+
|
11110 |
+
if (!isset($slot)) {
|
11111 |
+
throw new NotSupportedException(
|
11112 |
+
"Cannot use '{$command->getId()}' over clusters of connections."
|
11113 |
+
);
|
11114 |
+
}
|
11115 |
+
|
11116 |
+
$node = $this->distributor->getBySlot($slot);
|
11117 |
+
|
11118 |
+
return $node;
|
11119 |
+
}
|
11120 |
+
|
11121 |
+
/**
|
11122 |
+
* {@inheritdoc}
|
11123 |
+
*/
|
11124 |
+
public function getConnectionById($connectionID)
|
11125 |
+
{
|
11126 |
+
return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null;
|
11127 |
+
}
|
11128 |
+
|
11129 |
+
/**
|
11130 |
+
* Retrieves a connection instance from the cluster using a key.
|
11131 |
+
*
|
11132 |
+
* @param string $key Key string.
|
11133 |
+
*
|
11134 |
+
* @return NodeConnectionInterface
|
11135 |
+
*/
|
11136 |
+
public function getConnectionByKey($key)
|
11137 |
+
{
|
11138 |
+
$hash = $this->strategy->getSlotByKey($key);
|
11139 |
+
$node = $this->distributor->getBySlot($hash);
|
11140 |
+
|
11141 |
+
return $node;
|
11142 |
+
}
|
11143 |
+
|
11144 |
+
/**
|
11145 |
+
* Returns the underlying command hash strategy used to hash commands by
|
11146 |
+
* using keys found in their arguments.
|
11147 |
+
*
|
11148 |
+
* @return StrategyInterface
|
11149 |
+
*/
|
11150 |
+
public function getClusterStrategy()
|
11151 |
+
{
|
11152 |
+
return $this->strategy;
|
11153 |
+
}
|
11154 |
+
|
11155 |
+
/**
|
11156 |
+
* {@inheritdoc}
|
11157 |
+
*/
|
11158 |
+
public function count()
|
11159 |
+
{
|
11160 |
+
return count($this->pool);
|
11161 |
+
}
|
11162 |
+
|
11163 |
+
/**
|
11164 |
+
* {@inheritdoc}
|
11165 |
+
*/
|
11166 |
+
public function getIterator()
|
11167 |
+
{
|
11168 |
+
return new ArrayIterator($this->pool);
|
11169 |
+
}
|
11170 |
+
|
11171 |
+
/**
|
11172 |
+
* {@inheritdoc}
|
11173 |
+
*/
|
11174 |
+
public function writeRequest(CommandInterface $command)
|
11175 |
+
{
|
11176 |
+
$this->getConnection($command)->writeRequest($command);
|
11177 |
+
}
|
11178 |
+
|
11179 |
+
/**
|
11180 |
+
* {@inheritdoc}
|
11181 |
+
*/
|
11182 |
+
public function readResponse(CommandInterface $command)
|
11183 |
+
{
|
11184 |
+
return $this->getConnection($command)->readResponse($command);
|
11185 |
+
}
|
11186 |
+
|
11187 |
+
/**
|
11188 |
+
* {@inheritdoc}
|
11189 |
+
*/
|
11190 |
+
public function executeCommand(CommandInterface $command)
|
11191 |
+
{
|
11192 |
+
return $this->getConnection($command)->executeCommand($command);
|
11193 |
+
}
|
11194 |
+
|
11195 |
+
/**
|
11196 |
+
* Executes the specified Redis command on all the nodes of a cluster.
|
11197 |
+
*
|
11198 |
+
* @param CommandInterface $command A Redis command.
|
11199 |
+
*
|
11200 |
+
* @return array
|
11201 |
+
*/
|
11202 |
+
public function executeCommandOnNodes(CommandInterface $command)
|
11203 |
+
{
|
11204 |
+
$responses = array();
|
11205 |
+
foreach ($this->pool as $connection) {
|
11206 |
+
$responses[] = $connection->executeCommand($command);
|
11207 |
+
}
|
11208 |
+
|
11209 |
+
return $responses;
|
11210 |
+
}
|
11211 |
+
}
|
11212 |
+
|
11213 |
+
/**
|
11214 |
+
* Aggregate connection handling replication of Redis nodes configured in a
|
11215 |
+
* single master / multiple slaves setup.
|
11216 |
+
*
|
11217 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11218 |
+
*/
|
11219 |
+
class MasterSlaveReplication implements ReplicationInterface
|
11220 |
+
{
|
11221 |
+
protected $strategy;
|
11222 |
+
protected $master;
|
11223 |
+
protected $slaves;
|
11224 |
+
protected $current;
|
11225 |
+
|
11226 |
+
/**
|
11227 |
+
* {@inheritdoc}
|
11228 |
+
*/
|
11229 |
+
public function __construct(ReplicationStrategy $strategy = null)
|
11230 |
+
{
|
11231 |
+
$this->slaves = array();
|
11232 |
+
$this->strategy = $strategy ?: new ReplicationStrategy();
|
11233 |
+
}
|
11234 |
+
|
11235 |
+
/**
|
11236 |
+
* Checks if one master and at least one slave have been defined.
|
11237 |
+
*/
|
11238 |
+
protected function check()
|
11239 |
+
{
|
11240 |
+
if (!isset($this->master) || !$this->slaves) {
|
11241 |
+
throw new RuntimeException('Replication needs one master and at least one slave.');
|
11242 |
+
}
|
11243 |
+
}
|
11244 |
+
|
11245 |
+
/**
|
11246 |
+
* Resets the connection state.
|
11247 |
+
*/
|
11248 |
+
protected function reset()
|
11249 |
+
{
|
11250 |
+
$this->current = null;
|
11251 |
+
}
|
11252 |
+
|
11253 |
+
/**
|
11254 |
+
* {@inheritdoc}
|
11255 |
+
*/
|
11256 |
+
public function add(NodeConnectionInterface $connection)
|
11257 |
+
{
|
11258 |
+
$alias = $connection->getParameters()->alias;
|
11259 |
+
|
11260 |
+
if ($alias === 'master') {
|
11261 |
+
$this->master = $connection;
|
11262 |
+
} else {
|
11263 |
+
$this->slaves[$alias ?: count($this->slaves)] = $connection;
|
11264 |
+
}
|
11265 |
+
|
11266 |
+
$this->reset();
|
11267 |
+
}
|
11268 |
+
|
11269 |
+
/**
|
11270 |
+
* {@inheritdoc}
|
11271 |
+
*/
|
11272 |
+
public function remove(NodeConnectionInterface $connection)
|
11273 |
+
{
|
11274 |
+
if ($connection->getParameters()->alias === 'master') {
|
11275 |
+
$this->master = null;
|
11276 |
+
$this->reset();
|
11277 |
+
|
11278 |
+
return true;
|
11279 |
+
} else {
|
11280 |
+
if (($id = array_search($connection, $this->slaves, true)) !== false) {
|
11281 |
+
unset($this->slaves[$id]);
|
11282 |
+
$this->reset();
|
11283 |
+
|
11284 |
+
return true;
|
11285 |
+
}
|
11286 |
+
}
|
11287 |
+
|
11288 |
+
return false;
|
11289 |
+
}
|
11290 |
+
|
11291 |
+
/**
|
11292 |
+
* {@inheritdoc}
|
11293 |
+
*/
|
11294 |
+
public function getConnection(CommandInterface $command)
|
11295 |
+
{
|
11296 |
+
if ($this->current === null) {
|
11297 |
+
$this->check();
|
11298 |
+
$this->current = $this->strategy->isReadOperation($command)
|
11299 |
+
? $this->pickSlave()
|
11300 |
+
: $this->master;
|
11301 |
+
|
11302 |
+
return $this->current;
|
11303 |
+
}
|
11304 |
+
|
11305 |
+
if ($this->current === $this->master) {
|
11306 |
+
return $this->current;
|
11307 |
+
}
|
11308 |
+
|
11309 |
+
if (!$this->strategy->isReadOperation($command)) {
|
11310 |
+
$this->current = $this->master;
|
11311 |
+
}
|
11312 |
+
|
11313 |
+
return $this->current;
|
11314 |
+
}
|
11315 |
+
|
11316 |
+
/**
|
11317 |
+
* {@inheritdoc}
|
11318 |
+
*/
|
11319 |
+
public function getConnectionById($connectionId)
|
11320 |
+
{
|
11321 |
+
if ($connectionId === 'master') {
|
11322 |
+
return $this->master;
|
11323 |
+
}
|
11324 |
+
|
11325 |
+
if (isset($this->slaves[$connectionId])) {
|
11326 |
+
return $this->slaves[$connectionId];
|
11327 |
+
}
|
11328 |
+
|
11329 |
+
return null;
|
11330 |
+
}
|
11331 |
+
|
11332 |
+
/**
|
11333 |
+
* {@inheritdoc}
|
11334 |
+
*/
|
11335 |
+
public function switchTo($connection)
|
11336 |
+
{
|
11337 |
+
$this->check();
|
11338 |
+
|
11339 |
+
if (!$connection instanceof NodeConnectionInterface) {
|
11340 |
+
$connection = $this->getConnectionById($connection);
|
11341 |
+
}
|
11342 |
+
if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
|
11343 |
+
throw new InvalidArgumentException('Invalid connection or connection not found.');
|
11344 |
+
}
|
11345 |
+
|
11346 |
+
$this->current = $connection;
|
11347 |
+
}
|
11348 |
+
|
11349 |
+
/**
|
11350 |
+
* {@inheritdoc}
|
11351 |
+
*/
|
11352 |
+
public function getCurrent()
|
11353 |
+
{
|
11354 |
+
return $this->current;
|
11355 |
+
}
|
11356 |
+
|
11357 |
+
/**
|
11358 |
+
* {@inheritdoc}
|
11359 |
+
*/
|
11360 |
+
public function getMaster()
|
11361 |
+
{
|
11362 |
+
return $this->master;
|
11363 |
+
}
|
11364 |
+
|
11365 |
+
/**
|
11366 |
+
* {@inheritdoc}
|
11367 |
+
*/
|
11368 |
+
public function getSlaves()
|
11369 |
+
{
|
11370 |
+
return array_values($this->slaves);
|
11371 |
+
}
|
11372 |
+
|
11373 |
+
/**
|
11374 |
+
* Returns the underlying replication strategy.
|
11375 |
+
*
|
11376 |
+
* @return ReplicationStrategy
|
11377 |
+
*/
|
11378 |
+
public function getReplicationStrategy()
|
11379 |
+
{
|
11380 |
+
return $this->strategy;
|
11381 |
+
}
|
11382 |
+
|
11383 |
+
/**
|
11384 |
+
* Returns a random slave.
|
11385 |
+
*
|
11386 |
+
* @return NodeConnectionInterface
|
11387 |
+
*/
|
11388 |
+
protected function pickSlave()
|
11389 |
+
{
|
11390 |
+
return $this->slaves[array_rand($this->slaves)];
|
11391 |
+
}
|
11392 |
+
|
11393 |
+
/**
|
11394 |
+
* {@inheritdoc}
|
11395 |
+
*/
|
11396 |
+
public function isConnected()
|
11397 |
+
{
|
11398 |
+
return $this->current ? $this->current->isConnected() : false;
|
11399 |
+
}
|
11400 |
+
|
11401 |
+
/**
|
11402 |
+
* {@inheritdoc}
|
11403 |
+
*/
|
11404 |
+
public function connect()
|
11405 |
+
{
|
11406 |
+
if ($this->current === null) {
|
11407 |
+
$this->check();
|
11408 |
+
$this->current = $this->pickSlave();
|
11409 |
+
}
|
11410 |
+
|
11411 |
+
$this->current->connect();
|
11412 |
+
}
|
11413 |
+
|
11414 |
+
/**
|
11415 |
+
* {@inheritdoc}
|
11416 |
+
*/
|
11417 |
+
public function disconnect()
|
11418 |
+
{
|
11419 |
+
if ($this->master) {
|
11420 |
+
$this->master->disconnect();
|
11421 |
+
}
|
11422 |
+
|
11423 |
+
foreach ($this->slaves as $connection) {
|
11424 |
+
$connection->disconnect();
|
11425 |
+
}
|
11426 |
+
}
|
11427 |
+
|
11428 |
+
/**
|
11429 |
+
* {@inheritdoc}
|
11430 |
+
*/
|
11431 |
+
public function writeRequest(CommandInterface $command)
|
11432 |
+
{
|
11433 |
+
$this->getConnection($command)->writeRequest($command);
|
11434 |
+
}
|
11435 |
+
|
11436 |
+
/**
|
11437 |
+
* {@inheritdoc}
|
11438 |
+
*/
|
11439 |
+
public function readResponse(CommandInterface $command)
|
11440 |
+
{
|
11441 |
+
return $this->getConnection($command)->readResponse($command);
|
11442 |
+
}
|
11443 |
+
|
11444 |
+
/**
|
11445 |
+
* {@inheritdoc}
|
11446 |
+
*/
|
11447 |
+
public function executeCommand(CommandInterface $command)
|
11448 |
+
{
|
11449 |
+
return $this->getConnection($command)->executeCommand($command);
|
11450 |
+
}
|
11451 |
+
|
11452 |
+
/**
|
11453 |
+
* {@inheritdoc}
|
11454 |
+
*/
|
11455 |
+
public function __sleep()
|
11456 |
+
{
|
11457 |
+
return array('master', 'slaves', 'strategy');
|
11458 |
+
}
|
11459 |
+
}
|
11460 |
+
|
11461 |
+
/* --------------------------------------------------------------------------- */
|
11462 |
+
|
11463 |
+
namespace Predis\Pipeline;
|
11464 |
+
|
11465 |
+
use SplQueue;
|
11466 |
+
use Predis\ClientException;
|
11467 |
+
use Predis\ClientInterface;
|
11468 |
+
use Predis\Connection\ConnectionInterface;
|
11469 |
+
use Predis\Connection\NodeConnectionInterface;
|
11470 |
+
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
11471 |
+
use Predis\Response\ResponseInterface;
|
11472 |
+
use Predis\Response\ServerException;
|
11473 |
+
use Predis\NotSupportedException;
|
11474 |
+
use Predis\CommunicationException;
|
11475 |
+
use Predis\Connection\Aggregate\ClusterInterface;
|
11476 |
+
use Exception;
|
11477 |
+
use InvalidArgumentException;
|
11478 |
+
use Predis\ClientContextInterface;
|
11479 |
+
use Predis\Command\CommandInterface;
|
11480 |
+
use Predis\Connection\Aggregate\ReplicationInterface;
|
11481 |
+
|
11482 |
+
/**
|
11483 |
+
* Implementation of a command pipeline in which write and read operations of
|
11484 |
+
* Redis commands are pipelined to alleviate the effects of network round-trips.
|
11485 |
+
*
|
11486 |
+
* {@inheritdoc}
|
11487 |
+
*
|
11488 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11489 |
+
*/
|
11490 |
+
class Pipeline implements ClientContextInterface
|
11491 |
+
{
|
11492 |
+
private $client;
|
11493 |
+
private $pipeline;
|
11494 |
+
|
11495 |
+
private $responses = array();
|
11496 |
+
private $running = false;
|
11497 |
+
|
11498 |
+
/**
|
11499 |
+
* @param ClientInterface $client Client instance used by the context.
|
11500 |
+
*/
|
11501 |
+
public function __construct(ClientInterface $client)
|
11502 |
+
{
|
11503 |
+
$this->client = $client;
|
11504 |
+
$this->pipeline = new SplQueue();
|
11505 |
+
}
|
11506 |
+
|
11507 |
+
/**
|
11508 |
+
* Queues a command into the pipeline buffer.
|
11509 |
+
*
|
11510 |
+
* @param string $method Command ID.
|
11511 |
+
* @param array $arguments Arguments for the command.
|
11512 |
+
*
|
11513 |
+
* @return $this
|
11514 |
+
*/
|
11515 |
+
public function __call($method, $arguments)
|
11516 |
+
{
|
11517 |
+
$command = $this->client->createCommand($method, $arguments);
|
11518 |
+
$this->recordCommand($command);
|
11519 |
+
|
11520 |
+
return $this;
|
11521 |
+
}
|
11522 |
+
|
11523 |
+
/**
|
11524 |
+
* Queues a command instance into the pipeline buffer.
|
11525 |
+
*
|
11526 |
+
* @param CommandInterface $command Command to be queued in the buffer.
|
11527 |
+
*/
|
11528 |
+
protected function recordCommand(CommandInterface $command)
|
11529 |
+
{
|
11530 |
+
$this->pipeline->enqueue($command);
|
11531 |
+
}
|
11532 |
+
|
11533 |
+
/**
|
11534 |
+
* Queues a command instance into the pipeline buffer.
|
11535 |
+
*
|
11536 |
+
* @param CommandInterface $command Command instance to be queued in the buffer.
|
11537 |
+
*
|
11538 |
+
* @return $this
|
11539 |
+
*/
|
11540 |
+
public function executeCommand(CommandInterface $command)
|
11541 |
+
{
|
11542 |
+
$this->recordCommand($command);
|
11543 |
+
|
11544 |
+
return $this;
|
11545 |
+
}
|
11546 |
+
|
11547 |
+
/**
|
11548 |
+
* Throws an exception on -ERR responses returned by Redis.
|
11549 |
+
*
|
11550 |
+
* @param ConnectionInterface $connection Redis connection that returned the error.
|
11551 |
+
* @param ErrorResponseInterface $response Instance of the error response.
|
11552 |
+
*
|
11553 |
+
* @throws ServerException
|
11554 |
+
*/
|
11555 |
+
protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response)
|
11556 |
+
{
|
11557 |
+
$connection->disconnect();
|
11558 |
+
$message = $response->getMessage();
|
11559 |
+
|
11560 |
+
throw new ServerException($message);
|
11561 |
+
}
|
11562 |
+
|
11563 |
+
/**
|
11564 |
+
* Returns the underlying connection to be used by the pipeline.
|
11565 |
+
*
|
11566 |
+
* @return ConnectionInterface
|
11567 |
+
*/
|
11568 |
+
protected function getConnection()
|
11569 |
+
{
|
11570 |
+
$connection = $this->getClient()->getConnection();
|
11571 |
+
|
11572 |
+
if ($connection instanceof ReplicationInterface) {
|
11573 |
+
$connection->switchTo('master');
|
11574 |
+
}
|
11575 |
+
|
11576 |
+
return $connection;
|
11577 |
+
}
|
11578 |
+
|
11579 |
+
/**
|
11580 |
+
* Implements the logic to flush the queued commands and read the responses
|
11581 |
+
* from the current connection.
|
11582 |
+
*
|
11583 |
+
* @param ConnectionInterface $connection Current connection instance.
|
11584 |
+
* @param SplQueue $commands Queued commands.
|
11585 |
+
*
|
11586 |
+
* @return array
|
11587 |
+
*/
|
11588 |
+
protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
|
11589 |
+
{
|
11590 |
+
foreach ($commands as $command) {
|
11591 |
+
$connection->writeRequest($command);
|
11592 |
+
}
|
11593 |
+
|
11594 |
+
$responses = array();
|
11595 |
+
$exceptions = $this->throwServerExceptions();
|
11596 |
+
|
11597 |
+
while (!$commands->isEmpty()) {
|
11598 |
+
$command = $commands->dequeue();
|
11599 |
+
$response = $connection->readResponse($command);
|
11600 |
+
|
11601 |
+
if (!$response instanceof ResponseInterface) {
|
11602 |
+
$responses[] = $command->parseResponse($response);
|
11603 |
+
} elseif ($response instanceof ErrorResponseInterface && $exceptions) {
|
11604 |
+
$this->exception($connection, $response);
|
11605 |
+
} else {
|
11606 |
+
$responses[] = $response;
|
11607 |
+
}
|
11608 |
+
}
|
11609 |
+
|
11610 |
+
return $responses;
|
11611 |
+
}
|
11612 |
+
|
11613 |
+
/**
|
11614 |
+
* Flushes the buffer holding all of the commands queued so far.
|
11615 |
+
*
|
11616 |
+
* @param bool $send Specifies if the commands in the buffer should be sent to Redis.
|
11617 |
+
*
|
11618 |
+
* @return $this
|
11619 |
+
*/
|
11620 |
+
public function flushPipeline($send = true)
|
11621 |
+
{
|
11622 |
+
if ($send && !$this->pipeline->isEmpty()) {
|
11623 |
+
$responses = $this->executePipeline($this->getConnection(), $this->pipeline);
|
11624 |
+
$this->responses = array_merge($this->responses, $responses);
|
11625 |
+
} else {
|
11626 |
+
$this->pipeline = new SplQueue();
|
11627 |
+
}
|
11628 |
+
|
11629 |
+
return $this;
|
11630 |
+
}
|
11631 |
+
|
11632 |
+
/**
|
11633 |
+
* Marks the running status of the pipeline.
|
11634 |
+
*
|
11635 |
+
* @param bool $bool Sets the running status of the pipeline.
|
11636 |
+
*
|
11637 |
+
* @throws ClientException
|
11638 |
+
*/
|
11639 |
+
private function setRunning($bool)
|
11640 |
+
{
|
11641 |
+
if ($bool && $this->running) {
|
11642 |
+
throw new ClientException('The current pipeline context is already being executed.');
|
11643 |
+
}
|
11644 |
+
|
11645 |
+
$this->running = $bool;
|
11646 |
+
}
|
11647 |
+
|
11648 |
+
/**
|
11649 |
+
* Handles the actual execution of the whole pipeline.
|
11650 |
+
*
|
11651 |
+
* @param mixed $callable Optional callback for execution.
|
11652 |
+
*
|
11653 |
+
* @return array
|
11654 |
+
*
|
11655 |
+
* @throws Exception
|
11656 |
+
* @throws InvalidArgumentException
|
11657 |
+
*/
|
11658 |
+
public function execute($callable = null)
|
11659 |
+
{
|
11660 |
+
if ($callable && !is_callable($callable)) {
|
11661 |
+
throw new InvalidArgumentException('The argument must be a callable object.');
|
11662 |
+
}
|
11663 |
+
|
11664 |
+
$exception = null;
|
11665 |
+
$this->setRunning(true);
|
11666 |
+
|
11667 |
+
try {
|
11668 |
+
if ($callable) {
|
11669 |
+
call_user_func($callable, $this);
|
11670 |
+
}
|
11671 |
+
|
11672 |
+
$this->flushPipeline();
|
11673 |
+
} catch (Exception $exception) {
|
11674 |
+
// NOOP
|
11675 |
+
}
|
11676 |
+
|
11677 |
+
$this->setRunning(false);
|
11678 |
+
|
11679 |
+
if ($exception) {
|
11680 |
+
throw $exception;
|
11681 |
+
}
|
11682 |
+
|
11683 |
+
return $this->responses;
|
11684 |
+
}
|
11685 |
+
|
11686 |
+
/**
|
11687 |
+
* Returns if the pipeline should throw exceptions on server errors.
|
11688 |
+
*
|
11689 |
+
* @return bool
|
11690 |
+
*/
|
11691 |
+
protected function throwServerExceptions()
|
11692 |
+
{
|
11693 |
+
return (bool) $this->client->getOptions()->exceptions;
|
11694 |
+
}
|
11695 |
+
|
11696 |
+
/**
|
11697 |
+
* Returns the underlying client instance used by the pipeline object.
|
11698 |
+
*
|
11699 |
+
* @return ClientInterface
|
11700 |
+
*/
|
11701 |
+
public function getClient()
|
11702 |
+
{
|
11703 |
+
return $this->client;
|
11704 |
+
}
|
11705 |
+
}
|
11706 |
+
|
11707 |
+
/**
|
11708 |
+
* Command pipeline that writes commands to the servers but discards responses.
|
11709 |
+
*
|
11710 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11711 |
+
*/
|
11712 |
+
class FireAndForget extends Pipeline
|
11713 |
+
{
|
11714 |
+
/**
|
11715 |
+
* {@inheritdoc}
|
11716 |
+
*/
|
11717 |
+
protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
|
11718 |
+
{
|
11719 |
+
while (!$commands->isEmpty()) {
|
11720 |
+
$connection->writeRequest($commands->dequeue());
|
11721 |
+
}
|
11722 |
+
|
11723 |
+
$connection->disconnect();
|
11724 |
+
|
11725 |
+
return array();
|
11726 |
+
}
|
11727 |
+
}
|
11728 |
+
|
11729 |
+
/**
|
11730 |
+
* Command pipeline that does not throw exceptions on connection errors, but
|
11731 |
+
* returns the exception instances as the rest of the response elements.
|
11732 |
+
*
|
11733 |
+
* @todo Awful naming!
|
11734 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11735 |
+
*/
|
11736 |
+
class ConnectionErrorProof extends Pipeline
|
11737 |
+
{
|
11738 |
+
/**
|
11739 |
+
* {@inheritdoc}
|
11740 |
+
*/
|
11741 |
+
protected function getConnection()
|
11742 |
+
{
|
11743 |
+
return $this->getClient()->getConnection();
|
11744 |
+
}
|
11745 |
+
|
11746 |
+
/**
|
11747 |
+
* {@inheritdoc}
|
11748 |
+
*/
|
11749 |
+
protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
|
11750 |
+
{
|
11751 |
+
if ($connection instanceof NodeConnectionInterface) {
|
11752 |
+
return $this->executeSingleNode($connection, $commands);
|
11753 |
+
} elseif ($connection instanceof ClusterInterface) {
|
11754 |
+
return $this->executeCluster($connection, $commands);
|
11755 |
+
} else {
|
11756 |
+
$class = get_class($connection);
|
11757 |
+
|
11758 |
+
throw new NotSupportedException("The connection class '$class' is not supported.");
|
11759 |
+
}
|
11760 |
+
}
|
11761 |
+
|
11762 |
+
/**
|
11763 |
+
* {@inheritdoc}
|
11764 |
+
*/
|
11765 |
+
protected function executeSingleNode(NodeConnectionInterface $connection, SplQueue $commands)
|
11766 |
+
{
|
11767 |
+
$responses = array();
|
11768 |
+
$sizeOfPipe = count($commands);
|
11769 |
+
|
11770 |
+
foreach ($commands as $command) {
|
11771 |
+
try {
|
11772 |
+
$connection->writeRequest($command);
|
11773 |
+
} catch (CommunicationException $exception) {
|
11774 |
+
return array_fill(0, $sizeOfPipe, $exception);
|
11775 |
+
}
|
11776 |
+
}
|
11777 |
+
|
11778 |
+
for ($i = 0; $i < $sizeOfPipe; $i++) {
|
11779 |
+
$command = $commands->dequeue();
|
11780 |
+
|
11781 |
+
try {
|
11782 |
+
$responses[$i] = $connection->readResponse($command);
|
11783 |
+
} catch (CommunicationException $exception) {
|
11784 |
+
$add = count($commands) - count($responses);
|
11785 |
+
$responses = array_merge($responses, array_fill(0, $add, $exception));
|
11786 |
+
|
11787 |
+
break;
|
11788 |
+
}
|
11789 |
+
}
|
11790 |
+
|
11791 |
+
return $responses;
|
11792 |
+
}
|
11793 |
+
|
11794 |
+
/**
|
11795 |
+
* {@inheritdoc}
|
11796 |
+
*/
|
11797 |
+
protected function executeCluster(ClusterInterface $connection, SplQueue $commands)
|
11798 |
+
{
|
11799 |
+
$responses = array();
|
11800 |
+
$sizeOfPipe = count($commands);
|
11801 |
+
$exceptions = array();
|
11802 |
+
|
11803 |
+
foreach ($commands as $command) {
|
11804 |
+
$cmdConnection = $connection->getConnection($command);
|
11805 |
+
|
11806 |
+
if (isset($exceptions[spl_object_hash($cmdConnection)])) {
|
11807 |
+
continue;
|
11808 |
+
}
|
11809 |
+
|
11810 |
+
try {
|
11811 |
+
$cmdConnection->writeRequest($command);
|
11812 |
+
} catch (CommunicationException $exception) {
|
11813 |
+
$exceptions[spl_object_hash($cmdConnection)] = $exception;
|
11814 |
+
}
|
11815 |
+
}
|
11816 |
+
|
11817 |
+
for ($i = 0; $i < $sizeOfPipe; $i++) {
|
11818 |
+
$command = $commands->dequeue();
|
11819 |
+
|
11820 |
+
$cmdConnection = $connection->getConnection($command);
|
11821 |
+
$connectionHash = spl_object_hash($cmdConnection);
|
11822 |
+
|
11823 |
+
if (isset($exceptions[$connectionHash])) {
|
11824 |
+
$responses[$i] = $exceptions[$connectionHash];
|
11825 |
+
continue;
|
11826 |
+
}
|
11827 |
+
|
11828 |
+
try {
|
11829 |
+
$responses[$i] = $cmdConnection->readResponse($command);
|
11830 |
+
} catch (CommunicationException $exception) {
|
11831 |
+
$responses[$i] = $exception;
|
11832 |
+
$exceptions[$connectionHash] = $exception;
|
11833 |
+
}
|
11834 |
+
}
|
11835 |
+
|
11836 |
+
return $responses;
|
11837 |
+
}
|
11838 |
+
}
|
11839 |
+
|
11840 |
+
/**
|
11841 |
+
* Command pipeline wrapped into a MULTI / EXEC transaction.
|
11842 |
+
*
|
11843 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11844 |
+
*/
|
11845 |
+
class Atomic extends Pipeline
|
11846 |
+
{
|
11847 |
+
/**
|
11848 |
+
* {@inheritdoc}
|
11849 |
+
*/
|
11850 |
+
public function __construct(ClientInterface $client)
|
11851 |
+
{
|
11852 |
+
if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) {
|
11853 |
+
throw new ClientException(
|
11854 |
+
"The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'."
|
11855 |
+
);
|
11856 |
+
}
|
11857 |
+
|
11858 |
+
parent::__construct($client);
|
11859 |
+
}
|
11860 |
+
|
11861 |
+
/**
|
11862 |
+
* {@inheritdoc}
|
11863 |
+
*/
|
11864 |
+
protected function getConnection()
|
11865 |
+
{
|
11866 |
+
$connection = $this->getClient()->getConnection();
|
11867 |
+
|
11868 |
+
if (!$connection instanceof NodeConnectionInterface) {
|
11869 |
+
$class = __CLASS__;
|
11870 |
+
|
11871 |
+
throw new ClientException("The class '$class' does not support aggregate connections.");
|
11872 |
+
}
|
11873 |
+
|
11874 |
+
return $connection;
|
11875 |
+
}
|
11876 |
+
|
11877 |
+
/**
|
11878 |
+
* {@inheritdoc}
|
11879 |
+
*/
|
11880 |
+
protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
|
11881 |
+
{
|
11882 |
+
$profile = $this->getClient()->getProfile();
|
11883 |
+
$connection->executeCommand($profile->createCommand('multi'));
|
11884 |
+
|
11885 |
+
foreach ($commands as $command) {
|
11886 |
+
$connection->writeRequest($command);
|
11887 |
+
}
|
11888 |
+
|
11889 |
+
foreach ($commands as $command) {
|
11890 |
+
$response = $connection->readResponse($command);
|
11891 |
+
|
11892 |
+
if ($response instanceof ErrorResponseInterface) {
|
11893 |
+
$connection->executeCommand($profile->createCommand('discard'));
|
11894 |
+
throw new ServerException($response->getMessage());
|
11895 |
+
}
|
11896 |
+
}
|
11897 |
+
|
11898 |
+
$executed = $connection->executeCommand($profile->createCommand('exec'));
|
11899 |
+
|
11900 |
+
if (!isset($executed)) {
|
11901 |
+
// TODO: should be throwing a more appropriate exception.
|
11902 |
+
throw new ClientException(
|
11903 |
+
'The underlying transaction has been aborted by the server.'
|
11904 |
+
);
|
11905 |
+
}
|
11906 |
+
|
11907 |
+
if (count($executed) !== count($commands)) {
|
11908 |
+
$expected = count($commands);
|
11909 |
+
$received = count($executed);
|
11910 |
+
|
11911 |
+
throw new ClientException(
|
11912 |
+
"Invalid number of responses [expected $expected, received $received]."
|
11913 |
+
);
|
11914 |
+
}
|
11915 |
+
|
11916 |
+
$responses = array();
|
11917 |
+
$sizeOfPipe = count($commands);
|
11918 |
+
$exceptions = $this->throwServerExceptions();
|
11919 |
+
|
11920 |
+
for ($i = 0; $i < $sizeOfPipe; $i++) {
|
11921 |
+
$command = $commands->dequeue();
|
11922 |
+
$response = $executed[$i];
|
11923 |
+
|
11924 |
+
if (!$response instanceof ResponseInterface) {
|
11925 |
+
$responses[] = $command->parseResponse($response);
|
11926 |
+
} elseif ($response instanceof ErrorResponseInterface && $exceptions) {
|
11927 |
+
$this->exception($connection, $response);
|
11928 |
+
} else {
|
11929 |
+
$responses[] = $response;
|
11930 |
+
}
|
11931 |
+
|
11932 |
+
unset($executed[$i]);
|
11933 |
+
}
|
11934 |
+
|
11935 |
+
return $responses;
|
11936 |
+
}
|
11937 |
+
}
|
11938 |
+
|
11939 |
+
/* --------------------------------------------------------------------------- */
|
11940 |
+
|
11941 |
+
namespace Predis\Cluster\Distributor;
|
11942 |
+
|
11943 |
+
use Predis\Cluster\Hash\HashGeneratorInterface;
|
11944 |
+
use Exception;
|
11945 |
+
|
11946 |
+
/**
|
11947 |
+
* A distributor implements the logic to automatically distribute keys among
|
11948 |
+
* several nodes for client-side sharding.
|
11949 |
+
*
|
11950 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
11951 |
+
*/
|
11952 |
+
interface DistributorInterface
|
11953 |
+
{
|
11954 |
+
/**
|
11955 |
+
* Adds a node to the distributor with an optional weight.
|
11956 |
+
*
|
11957 |
+
* @param mixed $node Node object.
|
11958 |
+
* @param int $weight Weight for the node.
|
11959 |
+
*/
|
11960 |
+
public function add($node, $weight = null);
|
11961 |
+
|
11962 |
+
/**
|
11963 |
+
* Removes a node from the distributor.
|
11964 |
+
*
|
11965 |
+
* @param mixed $node Node object.
|
11966 |
+
*/
|
11967 |
+
public function remove($node);
|
11968 |
+
|
11969 |
+
/**
|
11970 |
+
* Returns the corresponding slot of a node from the distributor using the
|
11971 |
+
* computed hash of a key.
|
11972 |
+
*
|
11973 |
+
* @param mixed $hash
|
11974 |
+
*
|
11975 |
+
* @return mixed
|
11976 |
+
*/
|
11977 |
+
public function getSlot($hash);
|
11978 |
+
|
11979 |
+
/**
|
11980 |
+
* Returns a node from the distributor using its assigned slot ID.
|
11981 |
+
*
|
11982 |
+
* @param mixed $slot
|
11983 |
+
*
|
11984 |
+
* @return mixed|null
|
11985 |
+
*/
|
11986 |
+
public function getBySlot($slot);
|
11987 |
+
|
11988 |
+
/**
|
11989 |
+
* Returns a node from the distributor using the computed hash of a key.
|
11990 |
+
*
|
11991 |
+
* @param mixed $hash
|
11992 |
+
*
|
11993 |
+
* @return mixed
|
11994 |
+
*/
|
11995 |
+
public function getByHash($hash);
|
11996 |
+
|
11997 |
+
/**
|
11998 |
+
* Returns a node from the distributor mapping to the specified value.
|
11999 |
+
*
|
12000 |
+
* @param string $value
|
12001 |
+
*
|
12002 |
+
* @return mixed
|
12003 |
+
*/
|
12004 |
+
public function get($value);
|
12005 |
+
|
12006 |
+
/**
|
12007 |
+
* Returns the underlying hash generator instance.
|
12008 |
+
*
|
12009 |
+
* @return HashGeneratorInterface
|
12010 |
+
*/
|
12011 |
+
public function getHashGenerator();
|
12012 |
+
}
|
12013 |
+
|
12014 |
+
/**
|
12015 |
+
* This class implements an hashring-based distributor that uses the same
|
12016 |
+
* algorithm of memcache to distribute keys in a cluster using client-side
|
12017 |
+
* sharding.
|
12018 |
+
*
|
12019 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12020 |
+
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
12021 |
+
*/
|
12022 |
+
class HashRing implements DistributorInterface, HashGeneratorInterface
|
12023 |
+
{
|
12024 |
+
const DEFAULT_REPLICAS = 128;
|
12025 |
+
const DEFAULT_WEIGHT = 100;
|
12026 |
+
|
12027 |
+
private $ring;
|
12028 |
+
private $ringKeys;
|
12029 |
+
private $ringKeysCount;
|
12030 |
+
private $replicas;
|
12031 |
+
private $nodeHashCallback;
|
12032 |
+
private $nodes = array();
|
12033 |
+
|
12034 |
+
/**
|
12035 |
+
* @param int $replicas Number of replicas in the ring.
|
12036 |
+
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
|
12037 |
+
*/
|
12038 |
+
public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
|
12039 |
+
{
|
12040 |
+
$this->replicas = $replicas;
|
12041 |
+
$this->nodeHashCallback = $nodeHashCallback;
|
12042 |
+
}
|
12043 |
+
|
12044 |
+
/**
|
12045 |
+
* Adds a node to the ring with an optional weight.
|
12046 |
+
*
|
12047 |
+
* @param mixed $node Node object.
|
12048 |
+
* @param int $weight Weight for the node.
|
12049 |
+
*/
|
12050 |
+
public function add($node, $weight = null)
|
12051 |
+
{
|
12052 |
+
// In case of collisions in the hashes of the nodes, the node added
|
12053 |
+
// last wins, thus the order in which nodes are added is significant.
|
12054 |
+
$this->nodes[] = array(
|
12055 |
+
'object' => $node,
|
12056 |
+
'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT
|
12057 |
+
);
|
12058 |
+
|
12059 |
+
$this->reset();
|
12060 |
+
}
|
12061 |
+
|
12062 |
+
/**
|
12063 |
+
* {@inheritdoc}
|
12064 |
+
*/
|
12065 |
+
public function remove($node)
|
12066 |
+
{
|
12067 |
+
// A node is removed by resetting the ring so that it's recreated from
|
12068 |
+
// scratch, in order to reassign possible hashes with collisions to the
|
12069 |
+
// right node according to the order in which they were added in the
|
12070 |
+
// first place.
|
12071 |
+
for ($i = 0; $i < count($this->nodes); ++$i) {
|
12072 |
+
if ($this->nodes[$i]['object'] === $node) {
|
12073 |
+
array_splice($this->nodes, $i, 1);
|
12074 |
+
$this->reset();
|
12075 |
+
|
12076 |
+
break;
|
12077 |
+
}
|
12078 |
+
}
|
12079 |
+
}
|
12080 |
+
|
12081 |
+
/**
|
12082 |
+
* Resets the distributor.
|
12083 |
+
*/
|
12084 |
+
private function reset()
|
12085 |
+
{
|
12086 |
+
unset(
|
12087 |
+
$this->ring,
|
12088 |
+
$this->ringKeys,
|
12089 |
+
$this->ringKeysCount
|
12090 |
+
);
|
12091 |
+
}
|
12092 |
+
|
12093 |
+
/**
|
12094 |
+
* Returns the initialization status of the distributor.
|
12095 |
+
*
|
12096 |
+
* @return bool
|
12097 |
+
*/
|
12098 |
+
private function isInitialized()
|
12099 |
+
{
|
12100 |
+
return isset($this->ringKeys);
|
12101 |
+
}
|
12102 |
+
|
12103 |
+
/**
|
12104 |
+
* Calculates the total weight of all the nodes in the distributor.
|
12105 |
+
*
|
12106 |
+
* @return int
|
12107 |
+
*/
|
12108 |
+
private function computeTotalWeight()
|
12109 |
+
{
|
12110 |
+
$totalWeight = 0;
|
12111 |
+
|
12112 |
+
foreach ($this->nodes as $node) {
|
12113 |
+
$totalWeight += $node['weight'];
|
12114 |
+
}
|
12115 |
+
|
12116 |
+
return $totalWeight;
|
12117 |
+
}
|
12118 |
+
|
12119 |
+
/**
|
12120 |
+
* Initializes the distributor.
|
12121 |
+
*/
|
12122 |
+
private function initialize()
|
12123 |
+
{
|
12124 |
+
if ($this->isInitialized()) {
|
12125 |
+
return;
|
12126 |
+
}
|
12127 |
+
|
12128 |
+
if (!$this->nodes) {
|
12129 |
+
throw new EmptyRingException('Cannot initialize an empty hashring.');
|
12130 |
+
}
|
12131 |
+
|
12132 |
+
$this->ring = array();
|
12133 |
+
$totalWeight = $this->computeTotalWeight();
|
12134 |
+
$nodesCount = count($this->nodes);
|
12135 |
+
|
12136 |
+
foreach ($this->nodes as $node) {
|
12137 |
+
$weightRatio = $node['weight'] / $totalWeight;
|
12138 |
+
$this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
|
12139 |
+
}
|
12140 |
+
|
12141 |
+
ksort($this->ring, SORT_NUMERIC);
|
12142 |
+
$this->ringKeys = array_keys($this->ring);
|
12143 |
+
$this->ringKeysCount = count($this->ringKeys);
|
12144 |
+
}
|
12145 |
+
|
12146 |
+
/**
|
12147 |
+
* Implements the logic needed to add a node to the hashring.
|
12148 |
+
*
|
12149 |
+
* @param array $ring Source hashring.
|
12150 |
+
* @param mixed $node Node object to be added.
|
12151 |
+
* @param int $totalNodes Total number of nodes.
|
12152 |
+
* @param int $replicas Number of replicas in the ring.
|
12153 |
+
* @param float $weightRatio Weight ratio for the node.
|
12154 |
+
*/
|
12155 |
+
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
12156 |
+
{
|
12157 |
+
$nodeObject = $node['object'];
|
12158 |
+
$nodeHash = $this->getNodeHash($nodeObject);
|
12159 |
+
$replicas = (int) round($weightRatio * $totalNodes * $replicas);
|
12160 |
+
|
12161 |
+
for ($i = 0; $i < $replicas; $i++) {
|
12162 |
+
$key = crc32("$nodeHash:$i");
|
12163 |
+
$ring[$key] = $nodeObject;
|
12164 |
+
}
|
12165 |
+
}
|
12166 |
+
|
12167 |
+
/**
|
12168 |
+
* {@inheritdoc}
|
12169 |
+
*/
|
12170 |
+
protected function getNodeHash($nodeObject)
|
12171 |
+
{
|
12172 |
+
if (!isset($this->nodeHashCallback)) {
|
12173 |
+
return (string) $nodeObject;
|
12174 |
+
}
|
12175 |
+
|
12176 |
+
return call_user_func($this->nodeHashCallback, $nodeObject);
|
12177 |
+
}
|
12178 |
+
|
12179 |
+
/**
|
12180 |
+
* {@inheritdoc}
|
12181 |
+
*/
|
12182 |
+
public function hash($value)
|
12183 |
+
{
|
12184 |
+
return crc32($value);
|
12185 |
+
}
|
12186 |
+
|
12187 |
+
/**
|
12188 |
+
* {@inheritdoc}
|
12189 |
+
*/
|
12190 |
+
public function getByHash($hash)
|
12191 |
+
{
|
12192 |
+
return $this->ring[$this->getSlot($hash)];
|
12193 |
+
}
|
12194 |
+
|
12195 |
+
/**
|
12196 |
+
* {@inheritdoc}
|
12197 |
+
*/
|
12198 |
+
public function getBySlot($slot)
|
12199 |
+
{
|
12200 |
+
$this->initialize();
|
12201 |
+
|
12202 |
+
if (isset($this->ring[$slot])) {
|
12203 |
+
return $this->ring[$slot];
|
12204 |
+
}
|
12205 |
+
}
|
12206 |
+
|
12207 |
+
/**
|
12208 |
+
* {@inheritdoc}
|
12209 |
+
*/
|
12210 |
+
public function getSlot($hash)
|
12211 |
+
{
|
12212 |
+
$this->initialize();
|
12213 |
+
|
12214 |
+
$ringKeys = $this->ringKeys;
|
12215 |
+
$upper = $this->ringKeysCount - 1;
|
12216 |
+
$lower = 0;
|
12217 |
+
|
12218 |
+
while ($lower <= $upper) {
|
12219 |
+
$index = ($lower + $upper) >> 1;
|
12220 |
+
$item = $ringKeys[$index];
|
12221 |
+
|
12222 |
+
if ($item > $hash) {
|
12223 |
+
$upper = $index - 1;
|
12224 |
+
} elseif ($item < $hash) {
|
12225 |
+
$lower = $index + 1;
|
12226 |
+
} else {
|
12227 |
+
return $item;
|
12228 |
+
}
|
12229 |
+
}
|
12230 |
+
|
12231 |
+
return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
|
12232 |
+
}
|
12233 |
+
|
12234 |
+
/**
|
12235 |
+
* {@inheritdoc}
|
12236 |
+
*/
|
12237 |
+
public function get($value)
|
12238 |
+
{
|
12239 |
+
$hash = $this->hash($value);
|
12240 |
+
$node = $this->getByHash($hash);
|
12241 |
+
|
12242 |
+
return $node;
|
12243 |
+
}
|
12244 |
+
|
12245 |
+
/**
|
12246 |
+
* Implements a strategy to deal with wrap-around errors during binary searches.
|
12247 |
+
*
|
12248 |
+
* @param int $upper
|
12249 |
+
* @param int $lower
|
12250 |
+
* @param int $ringKeysCount
|
12251 |
+
*
|
12252 |
+
* @return int
|
12253 |
+
*/
|
12254 |
+
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
12255 |
+
{
|
12256 |
+
// Binary search for the last item in ringkeys with a value less or
|
12257 |
+
// equal to the key. If no such item exists, return the last item.
|
12258 |
+
return $upper >= 0 ? $upper : $ringKeysCount - 1;
|
12259 |
+
}
|
12260 |
+
|
12261 |
+
/**
|
12262 |
+
* {@inheritdoc}
|
12263 |
+
*/
|
12264 |
+
public function getHashGenerator()
|
12265 |
+
{
|
12266 |
+
return $this;
|
12267 |
+
}
|
12268 |
+
}
|
12269 |
+
|
12270 |
+
/**
|
12271 |
+
* This class implements an hashring-based distributor that uses the same
|
12272 |
+
* algorithm of libketama to distribute keys in a cluster using client-side
|
12273 |
+
* sharding.
|
12274 |
+
*
|
12275 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12276 |
+
* @author Lorenzo Castelli <lcastelli@gmail.com>
|
12277 |
+
*/
|
12278 |
+
class KetamaRing extends HashRing
|
12279 |
+
{
|
12280 |
+
const DEFAULT_REPLICAS = 160;
|
12281 |
+
|
12282 |
+
/**
|
12283 |
+
* @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
|
12284 |
+
*/
|
12285 |
+
public function __construct($nodeHashCallback = null)
|
12286 |
+
{
|
12287 |
+
parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
|
12288 |
+
}
|
12289 |
+
|
12290 |
+
/**
|
12291 |
+
* {@inheritdoc}
|
12292 |
+
*/
|
12293 |
+
protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
|
12294 |
+
{
|
12295 |
+
$nodeObject = $node['object'];
|
12296 |
+
$nodeHash = $this->getNodeHash($nodeObject);
|
12297 |
+
$replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
|
12298 |
+
|
12299 |
+
for ($i = 0; $i < $replicas; $i++) {
|
12300 |
+
$unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
|
12301 |
+
|
12302 |
+
foreach ($unpackedDigest as $key) {
|
12303 |
+
$ring[$key] = $nodeObject;
|
12304 |
+
}
|
12305 |
+
}
|
12306 |
+
}
|
12307 |
+
|
12308 |
+
/**
|
12309 |
+
* {@inheritdoc}
|
12310 |
+
*/
|
12311 |
+
public function hash($value)
|
12312 |
+
{
|
12313 |
+
$hash = unpack('V', md5($value, true));
|
12314 |
+
|
12315 |
+
return $hash[1];
|
12316 |
+
}
|
12317 |
+
|
12318 |
+
/**
|
12319 |
+
* {@inheritdoc}
|
12320 |
+
*/
|
12321 |
+
protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
|
12322 |
+
{
|
12323 |
+
// Binary search for the first item in ringkeys with a value greater
|
12324 |
+
// or equal to the key. If no such item exists, return the first item.
|
12325 |
+
return $lower < $ringKeysCount ? $lower : 0;
|
12326 |
+
}
|
12327 |
+
}
|
12328 |
+
|
12329 |
+
/**
|
12330 |
+
* Exception class that identifies empty rings.
|
12331 |
+
*
|
12332 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12333 |
+
*/
|
12334 |
+
class EmptyRingException extends Exception
|
12335 |
+
{
|
12336 |
+
}
|
12337 |
+
|
12338 |
+
/* --------------------------------------------------------------------------- */
|
12339 |
+
|
12340 |
+
namespace Predis\Response\Iterator;
|
12341 |
+
|
12342 |
+
use Predis\Connection\NodeConnectionInterface;
|
12343 |
+
use Iterator;
|
12344 |
+
use Countable;
|
12345 |
+
use Predis\Response\ResponseInterface;
|
12346 |
+
use OuterIterator;
|
12347 |
+
use InvalidArgumentException;
|
12348 |
+
use UnexpectedValueException;
|
12349 |
+
|
12350 |
+
/**
|
12351 |
+
* Iterator that abstracts the access to multibulk responses allowing them to be
|
12352 |
+
* consumed in a streamable fashion without keeping the whole payload in memory.
|
12353 |
+
*
|
12354 |
+
* This iterator does not support rewinding which means that the iteration, once
|
12355 |
+
* consumed, cannot be restarted.
|
12356 |
+
*
|
12357 |
+
* Always make sure that the whole iteration is consumed (or dropped) to prevent
|
12358 |
+
* protocol desynchronization issues.
|
12359 |
+
*
|
12360 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12361 |
+
*/
|
12362 |
+
abstract class MultiBulkIterator implements Iterator, Countable, ResponseInterface
|
12363 |
+
{
|
12364 |
+
protected $current;
|
12365 |
+
protected $position;
|
12366 |
+
protected $size;
|
12367 |
+
|
12368 |
+
/**
|
12369 |
+
* {@inheritdoc}
|
12370 |
+
*/
|
12371 |
+
public function rewind()
|
12372 |
+
{
|
12373 |
+
// NOOP
|
12374 |
+
}
|
12375 |
+
|
12376 |
+
/**
|
12377 |
+
* {@inheritdoc}
|
12378 |
+
*/
|
12379 |
+
public function current()
|
12380 |
+
{
|
12381 |
+
return $this->current;
|
12382 |
+
}
|
12383 |
+
|
12384 |
+
/**
|
12385 |
+
* {@inheritdoc}
|
12386 |
+
*/
|
12387 |
+
public function key()
|
12388 |
+
{
|
12389 |
+
return $this->position;
|
12390 |
+
}
|
12391 |
+
|
12392 |
+
/**
|
12393 |
+
* {@inheritdoc}
|
12394 |
+
*/
|
12395 |
+
public function next()
|
12396 |
+
{
|
12397 |
+
if (++$this->position < $this->size) {
|
12398 |
+
$this->current = $this->getValue();
|
12399 |
+
}
|
12400 |
+
}
|
12401 |
+
|
12402 |
+
/**
|
12403 |
+
* {@inheritdoc}
|
12404 |
+
*/
|
12405 |
+
public function valid()
|
12406 |
+
{
|
12407 |
+
return $this->position < $this->size;
|
12408 |
+
}
|
12409 |
+
|
12410 |
+
/**
|
12411 |
+
* Returns the number of items comprising the whole multibulk response.
|
12412 |
+
*
|
12413 |
+
* This method should be used instead of iterator_count() to get the size of
|
12414 |
+
* the current multibulk response since the former consumes the iteration to
|
12415 |
+
* count the number of elements, but our iterators do not support rewinding.
|
12416 |
+
*
|
12417 |
+
* @return int
|
12418 |
+
*/
|
12419 |
+
public function count()
|
12420 |
+
{
|
12421 |
+
return $this->size;
|
12422 |
+
}
|
12423 |
+
|
12424 |
+
/**
|
12425 |
+
* Returns the current position of the iterator.
|
12426 |
+
*
|
12427 |
+
* @return int
|
12428 |
+
*/
|
12429 |
+
public function getPosition()
|
12430 |
+
{
|
12431 |
+
return $this->position;
|
12432 |
+
}
|
12433 |
+
|
12434 |
+
/**
|
12435 |
+
* {@inheritdoc}
|
12436 |
+
*/
|
12437 |
+
abstract protected function getValue();
|
12438 |
+
}
|
12439 |
+
|
12440 |
+
/**
|
12441 |
+
* Streamable multibulk response.
|
12442 |
+
*
|
12443 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12444 |
+
*/
|
12445 |
+
class MultiBulk extends MultiBulkIterator
|
12446 |
+
{
|
12447 |
+
private $connection;
|
12448 |
+
|
12449 |
+
/**
|
12450 |
+
* @param NodeConnectionInterface $connection Connection to Redis.
|
12451 |
+
* @param int $size Number of elements of the multibulk response.
|
12452 |
+
*/
|
12453 |
+
public function __construct(NodeConnectionInterface $connection, $size)
|
12454 |
+
{
|
12455 |
+
$this->connection = $connection;
|
12456 |
+
$this->size = $size;
|
12457 |
+
$this->position = 0;
|
12458 |
+
$this->current = $size > 0 ? $this->getValue() : null;
|
12459 |
+
}
|
12460 |
+
|
12461 |
+
/**
|
12462 |
+
* Handles the synchronization of the client with the Redis protocol when
|
12463 |
+
* the garbage collector kicks in (e.g. when the iterator goes out of the
|
12464 |
+
* scope of a foreach or it is unset).
|
12465 |
+
*/
|
12466 |
+
public function __destruct()
|
12467 |
+
{
|
12468 |
+
$this->drop(true);
|
12469 |
+
}
|
12470 |
+
|
12471 |
+
/**
|
12472 |
+
* Drop queued elements that have not been read from the connection either
|
12473 |
+
* by consuming the rest of the multibulk response or quickly by closing the
|
12474 |
+
* underlying connection.
|
12475 |
+
*
|
12476 |
+
* @param bool $disconnect Consume the iterator or drop the connection.
|
12477 |
+
*/
|
12478 |
+
public function drop($disconnect = false)
|
12479 |
+
{
|
12480 |
+
if ($disconnect) {
|
12481 |
+
if ($this->valid()) {
|
12482 |
+
$this->position = $this->size;
|
12483 |
+
$this->connection->disconnect();
|
12484 |
+
}
|
12485 |
+
} else {
|
12486 |
+
while ($this->valid()) {
|
12487 |
+
$this->next();
|
12488 |
+
}
|
12489 |
+
}
|
12490 |
+
}
|
12491 |
+
|
12492 |
+
/**
|
12493 |
+
* Reads the next item of the multibulk response from the connection.
|
12494 |
+
*
|
12495 |
+
* @return mixed
|
12496 |
+
*/
|
12497 |
+
protected function getValue()
|
12498 |
+
{
|
12499 |
+
return $this->connection->read();
|
12500 |
+
}
|
12501 |
+
}
|
12502 |
+
|
12503 |
+
/**
|
12504 |
+
* Outer iterator consuming streamable multibulk responses by yielding tuples of
|
12505 |
+
* keys and values.
|
12506 |
+
*
|
12507 |
+
* This wrapper is useful for responses to commands such as `HGETALL` that can
|
12508 |
+
* be iterater as $key => $value pairs.
|
12509 |
+
*
|
12510 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12511 |
+
*/
|
12512 |
+
class MultiBulkTuple extends MultiBulk implements OuterIterator
|
12513 |
+
{
|
12514 |
+
private $iterator;
|
12515 |
+
|
12516 |
+
/**
|
12517 |
+
* @param MultiBulk $iterator Inner multibulk response iterator.
|
12518 |
+
*/
|
12519 |
+
public function __construct(MultiBulk $iterator)
|
12520 |
+
{
|
12521 |
+
$this->checkPreconditions($iterator);
|
12522 |
+
|
12523 |
+
$this->size = count($iterator) / 2;
|
12524 |
+
$this->iterator = $iterator;
|
12525 |
+
$this->position = $iterator->getPosition();
|
12526 |
+
$this->current = $this->size > 0 ? $this->getValue() : null;
|
12527 |
+
}
|
12528 |
+
|
12529 |
+
/**
|
12530 |
+
* Checks for valid preconditions.
|
12531 |
+
*
|
12532 |
+
* @param MultiBulk $iterator Inner multibulk response iterator.
|
12533 |
+
*
|
12534 |
+
* @throws \InvalidArgumentException
|
12535 |
+
* @throws \UnexpectedValueException
|
12536 |
+
*/
|
12537 |
+
protected function checkPreconditions(MultiBulk $iterator)
|
12538 |
+
{
|
12539 |
+
if ($iterator->getPosition() !== 0) {
|
12540 |
+
throw new InvalidArgumentException(
|
12541 |
+
'Cannot initialize a tuple iterator using an already initiated iterator.'
|
12542 |
+
);
|
12543 |
+
}
|
12544 |
+
|
12545 |
+
if (($size = count($iterator)) % 2 !== 0) {
|
12546 |
+
throw new UnexpectedValueException("Invalid response size for a tuple iterator.");
|
12547 |
+
}
|
12548 |
+
}
|
12549 |
+
|
12550 |
+
/**
|
12551 |
+
* {@inheritdoc}
|
12552 |
+
*/
|
12553 |
+
public function getInnerIterator()
|
12554 |
+
{
|
12555 |
+
return $this->iterator;
|
12556 |
+
}
|
12557 |
+
|
12558 |
+
/**
|
12559 |
+
* {@inheritdoc}
|
12560 |
+
*/
|
12561 |
+
public function __destruct()
|
12562 |
+
{
|
12563 |
+
$this->iterator->drop(true);
|
12564 |
+
}
|
12565 |
+
|
12566 |
+
/**
|
12567 |
+
* {@inheritdoc}
|
12568 |
+
*/
|
12569 |
+
protected function getValue()
|
12570 |
+
{
|
12571 |
+
$k = $this->iterator->current();
|
12572 |
+
$this->iterator->next();
|
12573 |
+
|
12574 |
+
$v = $this->iterator->current();
|
12575 |
+
$this->iterator->next();
|
12576 |
+
|
12577 |
+
return array($k, $v);
|
12578 |
+
}
|
12579 |
+
}
|
12580 |
+
|
12581 |
+
/* --------------------------------------------------------------------------- */
|
12582 |
+
|
12583 |
+
namespace Predis\Cluster\Hash;
|
12584 |
+
|
12585 |
+
/**
|
12586 |
+
* An hash generator implements the logic used to calculate the hash of a key to
|
12587 |
+
* distribute operations among Redis nodes.
|
12588 |
+
*
|
12589 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12590 |
+
*/
|
12591 |
+
interface HashGeneratorInterface
|
12592 |
+
{
|
12593 |
+
/**
|
12594 |
+
* Generates an hash from a string to be used for distribution.
|
12595 |
+
*
|
12596 |
+
* @param string $value String value.
|
12597 |
+
*
|
12598 |
+
* @return int
|
12599 |
+
*/
|
12600 |
+
public function hash($value);
|
12601 |
+
}
|
12602 |
+
|
12603 |
+
/**
|
12604 |
+
* Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
|
12605 |
+
*
|
12606 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12607 |
+
*/
|
12608 |
+
class CRC16 implements HashGeneratorInterface
|
12609 |
+
{
|
12610 |
+
private static $CCITT_16 = array(
|
12611 |
+
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
12612 |
+
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
12613 |
+
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
12614 |
+
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
12615 |
+
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
12616 |
+
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
12617 |
+
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
12618 |
+
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
12619 |
+
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
12620 |
+
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
12621 |
+
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
12622 |
+
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
12623 |
+
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
12624 |
+
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
12625 |
+
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
12626 |
+
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
12627 |
+
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
12628 |
+
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
12629 |
+
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
12630 |
+
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
12631 |
+
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
12632 |
+
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
12633 |
+
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
12634 |
+
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
12635 |
+
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
12636 |
+
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
12637 |
+
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
12638 |
+
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
12639 |
+
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
12640 |
+
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
12641 |
+
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
12642 |
+
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
|
12643 |
+
);
|
12644 |
+
|
12645 |
+
/**
|
12646 |
+
* {@inheritdoc}
|
12647 |
+
*/
|
12648 |
+
public function hash($value)
|
12649 |
+
{
|
12650 |
+
// CRC-CCITT-16 algorithm
|
12651 |
+
$crc = 0;
|
12652 |
+
$CCITT_16 = self::$CCITT_16;
|
12653 |
+
$strlen = strlen($value);
|
12654 |
+
|
12655 |
+
for ($i = 0; $i < $strlen; $i++) {
|
12656 |
+
$crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
|
12657 |
+
}
|
12658 |
+
|
12659 |
+
return $crc;
|
12660 |
+
}
|
12661 |
+
}
|
12662 |
+
|
12663 |
+
/* --------------------------------------------------------------------------- */
|
12664 |
+
|
12665 |
+
namespace Predis\Command\Processor;
|
12666 |
+
|
12667 |
+
use InvalidArgumentException;
|
12668 |
+
use Predis\Command\CommandInterface;
|
12669 |
+
use Predis\Command\PrefixableCommandInterface;
|
12670 |
+
use ArrayAccess;
|
12671 |
+
use ArrayIterator;
|
12672 |
+
|
12673 |
+
/**
|
12674 |
+
* A command processor processes Redis commands before they are sent to Redis.
|
12675 |
+
*
|
12676 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12677 |
+
*/
|
12678 |
+
interface ProcessorInterface
|
12679 |
+
{
|
12680 |
+
/**
|
12681 |
+
* Processes the given Redis command.
|
12682 |
+
*
|
12683 |
+
* @param CommandInterface $command Command instance.
|
12684 |
+
*/
|
12685 |
+
public function process(CommandInterface $command);
|
12686 |
+
}
|
12687 |
+
|
12688 |
+
/**
|
12689 |
+
* Default implementation of a command processors chain.
|
12690 |
+
*
|
12691 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12692 |
+
*/
|
12693 |
+
class ProcessorChain implements ArrayAccess, ProcessorInterface
|
12694 |
+
{
|
12695 |
+
private $processors = array();
|
12696 |
+
|
12697 |
+
/**
|
12698 |
+
* @param array $processors List of instances of ProcessorInterface.
|
12699 |
+
*/
|
12700 |
+
public function __construct($processors = array())
|
12701 |
+
{
|
12702 |
+
foreach ($processors as $processor) {
|
12703 |
+
$this->add($processor);
|
12704 |
+
}
|
12705 |
+
}
|
12706 |
+
|
12707 |
+
/**
|
12708 |
+
* {@inheritdoc}
|
12709 |
+
*/
|
12710 |
+
public function add(ProcessorInterface $processor)
|
12711 |
+
{
|
12712 |
+
$this->processors[] = $processor;
|
12713 |
+
}
|
12714 |
+
|
12715 |
+
/**
|
12716 |
+
* {@inheritdoc}
|
12717 |
+
*/
|
12718 |
+
public function remove(ProcessorInterface $processor)
|
12719 |
+
{
|
12720 |
+
if (false !== $index = array_search($processor, $this->processors, true)) {
|
12721 |
+
unset($this[$index]);
|
12722 |
+
}
|
12723 |
+
}
|
12724 |
+
|
12725 |
+
/**
|
12726 |
+
* {@inheritdoc}
|
12727 |
+
*/
|
12728 |
+
public function process(CommandInterface $command)
|
12729 |
+
{
|
12730 |
+
for ($i = 0; $i < $count = count($this->processors); $i++) {
|
12731 |
+
$this->processors[$i]->process($command);
|
12732 |
+
}
|
12733 |
+
}
|
12734 |
+
|
12735 |
+
/**
|
12736 |
+
* {@inheritdoc}
|
12737 |
+
*/
|
12738 |
+
public function getProcessors()
|
12739 |
+
{
|
12740 |
+
return $this->processors;
|
12741 |
+
}
|
12742 |
+
|
12743 |
+
/**
|
12744 |
+
* Returns an iterator over the list of command processor in the chain.
|
12745 |
+
*
|
12746 |
+
* @return ArrayIterator
|
12747 |
+
*/
|
12748 |
+
public function getIterator()
|
12749 |
+
{
|
12750 |
+
return new ArrayIterator($this->processors);
|
12751 |
+
}
|
12752 |
+
|
12753 |
+
/**
|
12754 |
+
* Returns the number of command processors in the chain.
|
12755 |
+
*
|
12756 |
+
* @return int
|
12757 |
+
*/
|
12758 |
+
public function count()
|
12759 |
+
{
|
12760 |
+
return count($this->processors);
|
12761 |
+
}
|
12762 |
+
|
12763 |
+
/**
|
12764 |
+
* {@inheritdoc}
|
12765 |
+
*/
|
12766 |
+
public function offsetExists($index)
|
12767 |
+
{
|
12768 |
+
return isset($this->processors[$index]);
|
12769 |
+
}
|
12770 |
+
|
12771 |
+
/**
|
12772 |
+
* {@inheritdoc}
|
12773 |
+
*/
|
12774 |
+
public function offsetGet($index)
|
12775 |
+
{
|
12776 |
+
return $this->processors[$index];
|
12777 |
+
}
|
12778 |
+
|
12779 |
+
/**
|
12780 |
+
* {@inheritdoc}
|
12781 |
+
*/
|
12782 |
+
public function offsetSet($index, $processor)
|
12783 |
+
{
|
12784 |
+
if (!$processor instanceof ProcessorInterface) {
|
12785 |
+
throw new InvalidArgumentException(
|
12786 |
+
"A processor chain accepts only instances of ".
|
12787 |
+
"'Predis\Command\Processor\ProcessorInterface'."
|
12788 |
+
);
|
12789 |
+
}
|
12790 |
+
|
12791 |
+
$this->processors[$index] = $processor;
|
12792 |
+
}
|
12793 |
+
|
12794 |
+
/**
|
12795 |
+
* {@inheritdoc}
|
12796 |
+
*/
|
12797 |
+
public function offsetUnset($index)
|
12798 |
+
{
|
12799 |
+
unset($this->processors[$index]);
|
12800 |
+
$this->processors = array_values($this->processors);
|
12801 |
+
}
|
12802 |
+
}
|
12803 |
+
|
12804 |
+
/**
|
12805 |
+
* Command processor capable of prefixing keys stored in the arguments of Redis
|
12806 |
+
* commands supported.
|
12807 |
+
*
|
12808 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
12809 |
+
*/
|
12810 |
+
class KeyPrefixProcessor implements ProcessorInterface
|
12811 |
+
{
|
12812 |
+
private $prefix;
|
12813 |
+
private $commands;
|
12814 |
+
|
12815 |
+
/**
|
12816 |
+
* @param string $prefix Prefix for the keys.
|
12817 |
+
*/
|
12818 |
+
public function __construct($prefix)
|
12819 |
+
{
|
12820 |
+
$this->prefix = $prefix;
|
12821 |
+
$this->commands = array(
|
12822 |
+
/* ---------------- Redis 1.2 ---------------- */
|
12823 |
+
'EXISTS' => 'self::first',
|
12824 |
+
'DEL' => 'self::all',
|
12825 |
+
'TYPE' => 'self::first',
|
12826 |
+
'KEYS' => 'self::first',
|
12827 |
+
'RENAME' => 'self::all',
|
12828 |
+
'RENAMENX' => 'self::all',
|
12829 |
+
'EXPIRE' => 'self::first',
|
12830 |
+
'EXPIREAT' => 'self::first',
|
12831 |
+
'TTL' => 'self::first',
|
12832 |
+
'MOVE' => 'self::first',
|
12833 |
+
'SORT' => 'self::sort',
|
12834 |
+
'DUMP' => 'self::first',
|
12835 |
+
'RESTORE' => 'self::first',
|
12836 |
+
'SET' => 'self::first',
|
12837 |
+
'SETNX' => 'self::first',
|
12838 |
+
'MSET' => 'self::interleaved',
|
12839 |
+
'MSETNX' => 'self::interleaved',
|
12840 |
+
'GET' => 'self::first',
|
12841 |
+
'MGET' => 'self::all',
|
12842 |
+
'GETSET' => 'self::first',
|
12843 |
+
'INCR' => 'self::first',
|
12844 |
+
'INCRBY' => 'self::first',
|
12845 |
+
'DECR' => 'self::first',
|
12846 |
+
'DECRBY' => 'self::first',
|
12847 |
+
'RPUSH' => 'self::first',
|
12848 |
+
'LPUSH' => 'self::first',
|
12849 |
+
'LLEN' => 'self::first',
|
12850 |
+
'LRANGE' => 'self::first',
|
12851 |
+
'LTRIM' => 'self::first',
|
12852 |
+
'LINDEX' => 'self::first',
|
12853 |
+
'LSET' => 'self::first',
|
12854 |
+
'LREM' => 'self::first',
|
12855 |
+
'LPOP' => 'self::first',
|
12856 |
+
'RPOP' => 'self::first',
|
12857 |
+
'RPOPLPUSH' => 'self::all',
|
12858 |
+
'SADD' => 'self::first',
|
12859 |
+
'SREM' => 'self::first',
|
12860 |
+
'SPOP' => 'self::first',
|
12861 |
+
'SMOVE' => 'self::skipLast',
|
12862 |
+
'SCARD' => 'self::first',
|
12863 |
+
'SISMEMBER' => 'self::first',
|
12864 |
+
'SINTER' => 'self::all',
|
12865 |
+
'SINTERSTORE' => 'self::all',
|
12866 |
+
'SUNION' => 'self::all',
|
12867 |
+
'SUNIONSTORE' => 'self::all',
|
12868 |
+
'SDIFF' => 'self::all',
|
12869 |
+
'SDIFFSTORE' => 'self::all',
|
12870 |
+
'SMEMBERS' => 'self::first',
|
12871 |
+
'SRANDMEMBER' => 'self::first',
|
12872 |
+
'ZADD' => 'self::first',
|
12873 |
+
'ZINCRBY' => 'self::first',
|
12874 |
+
'ZREM' => 'self::first',
|
12875 |
+
'ZRANGE' => 'self::first',
|
12876 |
+
'ZREVRANGE' => 'self::first',
|
12877 |
+
'ZRANGEBYSCORE' => 'self::first',
|
12878 |
+
'ZCARD' => 'self::first',
|
12879 |
+
'ZSCORE' => 'self::first',
|
12880 |
+
'ZREMRANGEBYSCORE' => 'self::first',
|
12881 |
+
/* ---------------- Redis 2.0 ---------------- */
|
12882 |
+
'SETEX' => 'self::first',
|
12883 |
+
'APPEND' => 'self::first',
|
12884 |
+
'SUBSTR' => 'self::first',
|
12885 |
+
'BLPOP' => 'self::skipLast',
|
12886 |
+
'BRPOP' => 'self::skipLast',
|
12887 |
+
'ZUNIONSTORE' => 'self::zsetStore',
|
12888 |
+
'ZINTERSTORE' => 'self::zsetStore',
|
12889 |
+
'ZCOUNT' => 'self::first',
|
12890 |
+
'ZRANK' => 'self::first',
|
12891 |
+
'ZREVRANK' => 'self::first',
|
12892 |
+
'ZREMRANGEBYRANK' => 'self::first',
|
12893 |
+
'HSET' => 'self::first',
|
12894 |
+
'HSETNX' => 'self::first',
|
12895 |
+
'HMSET' => 'self::first',
|
12896 |
+
'HINCRBY' => 'self::first',
|
12897 |
+
'HGET' => 'self::first',
|
12898 |
+
'HMGET' => 'self::first',
|
12899 |
+
'HDEL' => 'self::first',
|
12900 |
+
'HEXISTS' => 'self::first',
|
12901 |
+
'HLEN' => 'self::first',
|
12902 |
+
'HKEYS' => 'self::first',
|
12903 |
+
'HVALS' => 'self::first',
|
12904 |
+
'HGETALL' => 'self::first',
|
12905 |
+
'SUBSCRIBE' => 'self::all',
|
12906 |
+
'UNSUBSCRIBE' => 'self::all',
|
12907 |
+
'PSUBSCRIBE' => 'self::all',
|
12908 |
+
'PUNSUBSCRIBE' => 'self::all',
|
12909 |
+
'PUBLISH' => 'self::first',
|
12910 |
+
/* ---------------- Redis 2.2 ---------------- */
|
12911 |
+
'PERSIST' => 'self::first',
|
12912 |
+
'STRLEN' => 'self::first',
|
12913 |
+
'SETRANGE' => 'self::first',
|
12914 |
+
'GETRANGE' => 'self::first',
|
12915 |
+
'SETBIT' => 'self::first',
|
12916 |
+
'GETBIT' => 'self::first',
|
12917 |
+
'RPUSHX' => 'self::first',
|
12918 |
+
'LPUSHX' => 'self::first',
|
12919 |
+
'LINSERT' => 'self::first',
|
12920 |
+
'BRPOPLPUSH' => 'self::skipLast',
|
12921 |
+
'ZREVRANGEBYSCORE' => 'self::first',
|
12922 |
+
'WATCH' => 'self::all',
|
12923 |
+
/* ---------------- Redis 2.6 ---------------- */
|
12924 |
+
'PTTL' => 'self::first',
|
12925 |
+
'PEXPIRE' => 'self::first',
|
12926 |
+
'PEXPIREAT' => 'self::first',
|
12927 |
+
'PSETEX' => 'self::first',
|
12928 |
+
'INCRBYFLOAT' => 'self::first',
|
12929 |
+
'BITOP' => 'self::skipFirst',
|
12930 |
+
'BITCOUNT' => 'self::first',
|
12931 |
+
'HINCRBYFLOAT' => 'self::first',
|
12932 |
+
'EVAL' => 'self::evalKeys',
|
12933 |
+
'EVALSHA' => 'self::evalKeys',
|
12934 |
+
/* ---------------- Redis 2.8 ---------------- */
|
12935 |
+
'SSCAN' => 'self::first',
|
12936 |
+
'ZSCAN' => 'self::first',
|
12937 |
+
'HSCAN' => 'self::first',
|
12938 |
+
'PFADD' => 'self::first',
|
12939 |
+
'PFCOUNT' => 'self::all',
|
12940 |
+
'PFMERGE' => 'self::all',
|
12941 |
+
'ZLEXCOUNT' => 'self::first',
|
12942 |
+
'ZRANGEBYLEX' => 'self::first',
|
12943 |
+
'ZREMRANGEBYLEX' => 'self::first',
|
12944 |
+
);
|
12945 |
+
}
|
12946 |
+
|
12947 |
+
/**
|
12948 |
+
* Sets a prefix that is applied to all the keys.
|
12949 |
+
*
|
12950 |
+
* @param string $prefix Prefix for the keys.
|
12951 |
+
*/
|
12952 |
+
public function setPrefix($prefix)
|
12953 |
+
{
|
12954 |
+
$this->prefix = $prefix;
|
12955 |
+
}
|
12956 |
+
|
12957 |
+
/**
|
12958 |
+
* Gets the current prefix.
|
12959 |
+
*
|
12960 |
+
* @return string
|
12961 |
+
*/
|
12962 |
+
public function getPrefix()
|
12963 |
+
{
|
12964 |
+
return $this->prefix;
|
12965 |
+
}
|
12966 |
+
|
12967 |
+
/**
|
12968 |
+
* {@inheritdoc}
|
12969 |
+
*/
|
12970 |
+
public function process(CommandInterface $command)
|
12971 |
+
{
|
12972 |
+
if ($command instanceof PrefixableCommandInterface) {
|
12973 |
+
$command->prefixKeys($this->prefix);
|
12974 |
+
} elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
|
12975 |
+
call_user_func($this->commands[$commandID], $command, $this->prefix);
|
12976 |
+
}
|
12977 |
+
}
|
12978 |
+
|
12979 |
+
/**
|
12980 |
+
* Sets an handler for the specified command ID.
|
12981 |
+
*
|
12982 |
+
* The callback signature must have 2 parameters of the following types:
|
12983 |
+
*
|
12984 |
+
* - Predis\Command\CommandInterface (command instance)
|
12985 |
+
* - String (prefix)
|
12986 |
+
*
|
12987 |
+
* When the callback argument is omitted or NULL, the previously
|
12988 |
+
* associated handler for the specified command ID is removed.
|
12989 |
+
*
|
12990 |
+
* @param string $commandID The ID of the command to be handled.
|
12991 |
+
* @param mixed $callback A valid callable object or NULL.
|
12992 |
+
*
|
12993 |
+
* @throws \InvalidArgumentException
|
12994 |
+
*/
|
12995 |
+
public function setCommandHandler($commandID, $callback = null)
|
12996 |
+
{
|
12997 |
+
$commandID = strtoupper($commandID);
|
12998 |
+
|
12999 |
+
if (!isset($callback)) {
|
13000 |
+
unset($this->commands[$commandID]);
|
13001 |
+
|
13002 |
+
return;
|
13003 |
+
}
|
13004 |
+
|
13005 |
+
if (!is_callable($callback)) {
|
13006 |
+
throw new InvalidArgumentException(
|
13007 |
+
"Callback must be a valid callable object or NULL"
|
13008 |
+
);
|
13009 |
+
}
|
13010 |
+
|
13011 |
+
$this->commands[$commandID] = $callback;
|
13012 |
+
}
|
13013 |
+
|
13014 |
+
/**
|
13015 |
+
* {@inheritdoc}
|
13016 |
+
*/
|
13017 |
+
public function __toString()
|
13018 |
+
{
|
13019 |
+
return $this->getPrefix();
|
13020 |
+
}
|
13021 |
+
|
13022 |
+
/**
|
13023 |
+
* Applies the specified prefix only the first argument.
|
13024 |
+
*
|
13025 |
+
* @param CommandInterface $command Command instance.
|
13026 |
+
* @param string $prefix Prefix string.
|
13027 |
+
*/
|
13028 |
+
public static function first(CommandInterface $command, $prefix)
|
13029 |
+
{
|
13030 |
+
if ($arguments = $command->getArguments()) {
|
13031 |
+
$arguments[0] = "$prefix{$arguments[0]}";
|
13032 |
+
$command->setRawArguments($arguments);
|
13033 |
+
}
|
13034 |
+
}
|
13035 |
+
|
13036 |
+
/**
|
13037 |
+
* Applies the specified prefix to all the arguments.
|
13038 |
+
*
|
13039 |
+
* @param CommandInterface $command Command instance.
|
13040 |
+
* @param string $prefix Prefix string.
|
13041 |
+
*/
|
13042 |
+
public static function all(CommandInterface $command, $prefix)
|
13043 |
+
{
|
13044 |
+
if ($arguments = $command->getArguments()) {
|
13045 |
+
foreach ($arguments as &$key) {
|
13046 |
+
$key = "$prefix$key";
|
13047 |
+
}
|
13048 |
+
|
13049 |
+
$command->setRawArguments($arguments);
|
13050 |
+
}
|
13051 |
+
}
|
13052 |
+
|
13053 |
+
/**
|
13054 |
+
* Applies the specified prefix only to even arguments in the list.
|
13055 |
+
*
|
13056 |
+
* @param CommandInterface $command Command instance.
|
13057 |
+
* @param string $prefix Prefix string.
|
13058 |
+
*/
|
13059 |
+
public static function interleaved(CommandInterface $command, $prefix)
|
13060 |
+
{
|
13061 |
+
if ($arguments = $command->getArguments()) {
|
13062 |
+
$length = count($arguments);
|
13063 |
+
|
13064 |
+
for ($i = 0; $i < $length; $i += 2) {
|
13065 |
+
$arguments[$i] = "$prefix{$arguments[$i]}";
|
13066 |
+
}
|
13067 |
+
|
13068 |
+
$command->setRawArguments($arguments);
|
13069 |
+
}
|
13070 |
+
}
|
13071 |
+
|
13072 |
+
/**
|
13073 |
+
* Applies the specified prefix to all the arguments but the first one.
|
13074 |
+
*
|
13075 |
+
* @param CommandInterface $command Command instance.
|
13076 |
+
* @param string $prefix Prefix string.
|
13077 |
+
*/
|
13078 |
+
public static function skipFirst(CommandInterface $command, $prefix)
|
13079 |
+
{
|
13080 |
+
if ($arguments = $command->getArguments()) {
|
13081 |
+
$length = count($arguments);
|
13082 |
+
|
13083 |
+
for ($i = 1; $i < $length; $i++) {
|
13084 |
+
$arguments[$i] = "$prefix{$arguments[$i]}";
|
13085 |
+
}
|
13086 |
+
|
13087 |
+
$command->setRawArguments($arguments);
|
13088 |
+
}
|
13089 |
+
}
|
13090 |
+
|
13091 |
+
/**
|
13092 |
+
* Applies the specified prefix to all the arguments but the last one.
|
13093 |
+
*
|
13094 |
+
* @param CommandInterface $command Command instance.
|
13095 |
+
* @param string $prefix Prefix string.
|
13096 |
+
*/
|
13097 |
+
public static function skipLast(CommandInterface $command, $prefix)
|
13098 |
+
{
|
13099 |
+
if ($arguments = $command->getArguments()) {
|
13100 |
+
$length = count($arguments);
|
13101 |
+
|
13102 |
+
for ($i = 0; $i < $length - 1; $i++) {
|
13103 |
+
$arguments[$i] = "$prefix{$arguments[$i]}";
|
13104 |
+
}
|
13105 |
+
|
13106 |
+
$command->setRawArguments($arguments);
|
13107 |
+
}
|
13108 |
+
}
|
13109 |
+
|
13110 |
+
/**
|
13111 |
+
* Applies the specified prefix to the keys of a SORT command.
|
13112 |
+
*
|
13113 |
+
* @param CommandInterface $command Command instance.
|
13114 |
+
* @param string $prefix Prefix string.
|
13115 |
+
*/
|
13116 |
+
public static function sort(CommandInterface $command, $prefix)
|
13117 |
+
{
|
13118 |
+
if ($arguments = $command->getArguments()) {
|
13119 |
+
$arguments[0] = "$prefix{$arguments[0]}";
|
13120 |
+
|
13121 |
+
if (($count = count($arguments)) > 1) {
|
13122 |
+
for ($i = 1; $i < $count; $i++) {
|
13123 |
+
switch ($arguments[$i]) {
|
13124 |
+
case 'BY':
|
13125 |
+
case 'STORE':
|
13126 |
+
$arguments[$i] = "$prefix{$arguments[++$i]}";
|
13127 |
+
break;
|
13128 |
+
|
13129 |
+
case 'GET':
|
13130 |
+
$value = $arguments[++$i];
|
13131 |
+
if ($value !== '#') {
|
13132 |
+
$arguments[$i] = "$prefix$value";
|
13133 |
+
}
|
13134 |
+
break;
|
13135 |
+
|
13136 |
+
case 'LIMIT';
|
13137 |
+
$i += 2;
|
13138 |
+
break;
|
13139 |
+
}
|
13140 |
+
}
|
13141 |
+
}
|
13142 |
+
|
13143 |
+
$command->setRawArguments($arguments);
|
13144 |
+
}
|
13145 |
+
}
|
13146 |
+
|
13147 |
+
/**
|
13148 |
+
* Applies the specified prefix to the keys of an EVAL-based command.
|
13149 |
+
*
|
13150 |
+
* @param CommandInterface $command Command instance.
|
13151 |
+
* @param string $prefix Prefix string.
|
13152 |
+
*/
|
13153 |
+
public static function evalKeys(CommandInterface $command, $prefix)
|
13154 |
+
{
|
13155 |
+
if ($arguments = $command->getArguments()) {
|
13156 |
+
for ($i = 2; $i < $arguments[1] + 2; $i++) {
|
13157 |
+
$arguments[$i] = "$prefix{$arguments[$i]}";
|
13158 |
+
}
|
13159 |
+
|
13160 |
+
$command->setRawArguments($arguments);
|
13161 |
+
}
|
13162 |
+
}
|
13163 |
+
|
13164 |
+
/**
|
13165 |
+
* Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
|
13166 |
+
*
|
13167 |
+
* @param CommandInterface $command Command instance.
|
13168 |
+
* @param string $prefix Prefix string.
|
13169 |
+
*/
|
13170 |
+
public static function zsetStore(CommandInterface $command, $prefix)
|
13171 |
+
{
|
13172 |
+
if ($arguments = $command->getArguments()) {
|
13173 |
+
$arguments[0] = "$prefix{$arguments[0]}";
|
13174 |
+
$length = ((int) $arguments[1]) + 2;
|
13175 |
+
|
13176 |
+
for ($i = 2; $i < $length; $i++) {
|
13177 |
+
$arguments[$i] = "$prefix{$arguments[$i]}";
|
13178 |
+
}
|
13179 |
+
|
13180 |
+
$command->setRawArguments($arguments);
|
13181 |
+
}
|
13182 |
+
}
|
13183 |
+
}
|
13184 |
+
|
13185 |
+
/* --------------------------------------------------------------------------- */
|
13186 |
+
|
13187 |
+
namespace Predis\Protocol\Text;
|
13188 |
+
|
13189 |
+
use Predis\Command\CommandInterface;
|
13190 |
+
use Predis\Connection\CompositeConnectionInterface;
|
13191 |
+
use Predis\Protocol\ProtocolProcessorInterface;
|
13192 |
+
use Predis\Protocol\RequestSerializerInterface;
|
13193 |
+
use Predis\Protocol\ResponseReaderInterface;
|
13194 |
+
use Predis\CommunicationException;
|
13195 |
+
use Predis\Protocol\ProtocolException;
|
13196 |
+
use Predis\Response\Status as StatusResponse;
|
13197 |
+
use Predis\Response\Error as ErrorResponse;
|
13198 |
+
use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
|
13199 |
+
|
13200 |
+
/**
|
13201 |
+
* Response reader for the standard Redis wire protocol.
|
13202 |
+
*
|
13203 |
+
* @link http://redis.io/topics/protocol
|
13204 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13205 |
+
*/
|
13206 |
+
class ResponseReader implements ResponseReaderInterface
|
13207 |
+
{
|
13208 |
+
protected $handlers;
|
13209 |
+
|
13210 |
+
/**
|
13211 |
+
*
|
13212 |
+
*/
|
13213 |
+
public function __construct()
|
13214 |
+
{
|
13215 |
+
$this->handlers = $this->getDefaultHandlers();
|
13216 |
+
}
|
13217 |
+
|
13218 |
+
/**
|
13219 |
+
* Returns the default handlers for the supported type of responses.
|
13220 |
+
*
|
13221 |
+
* @return array
|
13222 |
+
*/
|
13223 |
+
protected function getDefaultHandlers()
|
13224 |
+
{
|
13225 |
+
return array(
|
13226 |
+
'+' => new Handler\StatusResponse(),
|
13227 |
+
'-' => new Handler\ErrorResponse(),
|
13228 |
+
':' => new Handler\IntegerResponse(),
|
13229 |
+
'$' => new Handler\BulkResponse(),
|
13230 |
+
'*' => new Handler\MultiBulkResponse(),
|
13231 |
+
);
|
13232 |
+
}
|
13233 |
+
|
13234 |
+
/**
|
13235 |
+
* Sets the handler for the specified prefix identifying the response type.
|
13236 |
+
*
|
13237 |
+
* @param string $prefix Identifier of the type of response.
|
13238 |
+
* @param Handler\ResponseHandlerInterface $handler Response handler.
|
13239 |
+
*/
|
13240 |
+
public function setHandler($prefix, Handler\ResponseHandlerInterface $handler)
|
13241 |
+
{
|
13242 |
+
$this->handlers[$prefix] = $handler;
|
13243 |
+
}
|
13244 |
+
|
13245 |
+
/**
|
13246 |
+
* Returns the response handler associated to a certain type of response.
|
13247 |
+
*
|
13248 |
+
* @param string $prefix Identifier of the type of response.
|
13249 |
+
*
|
13250 |
+
* @return Handler\ResponseHandlerInterface
|
13251 |
+
*/
|
13252 |
+
public function getHandler($prefix)
|
13253 |
+
{
|
13254 |
+
if (isset($this->handlers[$prefix])) {
|
13255 |
+
return $this->handlers[$prefix];
|
13256 |
+
}
|
13257 |
+
|
13258 |
+
return null;
|
13259 |
+
}
|
13260 |
+
|
13261 |
+
/**
|
13262 |
+
* {@inheritdoc}
|
13263 |
+
*/
|
13264 |
+
public function read(CompositeConnectionInterface $connection)
|
13265 |
+
{
|
13266 |
+
$header = $connection->readLine();
|
13267 |
+
|
13268 |
+
if ($header === '') {
|
13269 |
+
$this->onProtocolError($connection, 'Unexpected empty reponse header.');
|
13270 |
+
}
|
13271 |
+
|
13272 |
+
$prefix = $header[0];
|
13273 |
+
|
13274 |
+
if (!isset($this->handlers[$prefix])) {
|
13275 |
+
$this->onProtocolError($connection, "Unknown response prefix: '$prefix'.");
|
13276 |
+
}
|
13277 |
+
|
13278 |
+
$payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
|
13279 |
+
|
13280 |
+
return $payload;
|
13281 |
+
}
|
13282 |
+
|
13283 |
+
/**
|
13284 |
+
* Handles protocol errors generated while reading responses from a
|
13285 |
+
* connection.
|
13286 |
+
*
|
13287 |
+
* @param CompositeConnectionInterface $connection Redis connection that generated the error.
|
13288 |
+
* @param string $message Error message.
|
13289 |
+
*/
|
13290 |
+
protected function onProtocolError(CompositeConnectionInterface $connection, $message)
|
13291 |
+
{
|
13292 |
+
CommunicationException::handle(
|
13293 |
+
new ProtocolException($connection, $message)
|
13294 |
+
);
|
13295 |
+
}
|
13296 |
+
}
|
13297 |
+
|
13298 |
+
/**
|
13299 |
+
* Request serializer for the standard Redis wire protocol.
|
13300 |
+
*
|
13301 |
+
* @link http://redis.io/topics/protocol
|
13302 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13303 |
+
*/
|
13304 |
+
class RequestSerializer implements RequestSerializerInterface
|
13305 |
+
{
|
13306 |
+
/**
|
13307 |
+
* {@inheritdoc}
|
13308 |
+
*/
|
13309 |
+
public function serialize(CommandInterface $command)
|
13310 |
+
{
|
13311 |
+
$commandID = $command->getId();
|
13312 |
+
$arguments = $command->getArguments();
|
13313 |
+
|
13314 |
+
$cmdlen = strlen($commandID);
|
13315 |
+
$reqlen = count($arguments) + 1;
|
13316 |
+
|
13317 |
+
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
|
13318 |
+
|
13319 |
+
for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
|
13320 |
+
$argument = $arguments[$i];
|
13321 |
+
$arglen = strlen($argument);
|
13322 |
+
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
|
13323 |
+
}
|
13324 |
+
|
13325 |
+
return $buffer;
|
13326 |
+
}
|
13327 |
+
}
|
13328 |
+
|
13329 |
+
/**
|
13330 |
+
* Protocol processor for the standard Redis wire protocol.
|
13331 |
+
*
|
13332 |
+
* @link http://redis.io/topics/protocol
|
13333 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13334 |
+
*/
|
13335 |
+
class ProtocolProcessor implements ProtocolProcessorInterface
|
13336 |
+
{
|
13337 |
+
protected $mbiterable;
|
13338 |
+
protected $serializer;
|
13339 |
+
|
13340 |
+
/**
|
13341 |
+
*
|
13342 |
+
*/
|
13343 |
+
public function __construct()
|
13344 |
+
{
|
13345 |
+
$this->mbiterable = false;
|
13346 |
+
$this->serializer = new RequestSerializer();
|
13347 |
+
}
|
13348 |
+
|
13349 |
+
/**
|
13350 |
+
* {@inheritdoc}
|
13351 |
+
*/
|
13352 |
+
public function write(CompositeConnectionInterface $connection, CommandInterface $command)
|
13353 |
+
{
|
13354 |
+
$request = $this->serializer->serialize($command);
|
13355 |
+
$connection->writeBuffer($request);
|
13356 |
+
}
|
13357 |
+
|
13358 |
+
/**
|
13359 |
+
* {@inheritdoc}
|
13360 |
+
*/
|
13361 |
+
public function read(CompositeConnectionInterface $connection)
|
13362 |
+
{
|
13363 |
+
$chunk = $connection->readLine();
|
13364 |
+
$prefix = $chunk[0];
|
13365 |
+
$payload = substr($chunk, 1);
|
13366 |
+
|
13367 |
+
switch ($prefix) {
|
13368 |
+
case '+':
|
13369 |
+
return new StatusResponse($payload);
|
13370 |
+
|
13371 |
+
case '$':
|
13372 |
+
$size = (int) $payload;
|
13373 |
+
if ($size === -1) {
|
13374 |
+
return null;
|
13375 |
+
}
|
13376 |
+
|
13377 |
+
return substr($connection->readBuffer($size + 2), 0, -2);
|
13378 |
+
|
13379 |
+
case '*':
|
13380 |
+
$count = (int) $payload;
|
13381 |
+
|
13382 |
+
if ($count === -1) {
|
13383 |
+
return null;
|
13384 |
+
}
|
13385 |
+
if ($this->mbiterable) {
|
13386 |
+
return new MultiBulkIterator($connection, $count);
|
13387 |
+
}
|
13388 |
+
|
13389 |
+
$multibulk = array();
|
13390 |
+
|
13391 |
+
for ($i = 0; $i < $count; $i++) {
|
13392 |
+
$multibulk[$i] = $this->read($connection);
|
13393 |
+
}
|
13394 |
+
|
13395 |
+
return $multibulk;
|
13396 |
+
|
13397 |
+
case ':':
|
13398 |
+
return (int) $payload;
|
13399 |
+
|
13400 |
+
case '-':
|
13401 |
+
return new ErrorResponse($payload);
|
13402 |
+
|
13403 |
+
default:
|
13404 |
+
CommunicationException::handle(new ProtocolException(
|
13405 |
+
$connection, "Unknown response prefix: '$prefix'."
|
13406 |
+
));
|
13407 |
+
|
13408 |
+
return;
|
13409 |
+
}
|
13410 |
+
}
|
13411 |
+
|
13412 |
+
/**
|
13413 |
+
* Enables or disables returning multibulk responses as specialized PHP
|
13414 |
+
* iterators used to stream bulk elements of a multibulk response instead
|
13415 |
+
* returning a plain array.
|
13416 |
+
*
|
13417 |
+
* Streamable multibulk responses are not globally supported by the
|
13418 |
+
* abstractions built-in into Predis, such as transactions or pipelines.
|
13419 |
+
* Use them with care!
|
13420 |
+
*
|
13421 |
+
* @param bool $value Enable or disable streamable multibulk responses.
|
13422 |
+
*/
|
13423 |
+
public function useIterableMultibulk($value)
|
13424 |
+
{
|
13425 |
+
$this->mbiterable = (bool) $value;
|
13426 |
+
}
|
13427 |
+
}
|
13428 |
+
|
13429 |
+
/**
|
13430 |
+
* Composite protocol processor for the standard Redis wire protocol using
|
13431 |
+
* pluggable handlers to serialize requests and deserialize responses.
|
13432 |
+
*
|
13433 |
+
* @link http://redis.io/topics/protocol
|
13434 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13435 |
+
*/
|
13436 |
+
class CompositeProtocolProcessor implements ProtocolProcessorInterface
|
13437 |
+
{
|
13438 |
+
/*
|
13439 |
+
* @var RequestSerializerInterface
|
13440 |
+
*/
|
13441 |
+
protected $serializer;
|
13442 |
+
|
13443 |
+
/*
|
13444 |
+
* @var ResponseReaderInterface
|
13445 |
+
*/
|
13446 |
+
protected $reader;
|
13447 |
+
|
13448 |
+
/**
|
13449 |
+
* @param RequestSerializerInterface $serializer Request serializer.
|
13450 |
+
* @param ResponseReaderInterface $reader Response reader.
|
13451 |
+
*/
|
13452 |
+
public function __construct(
|
13453 |
+
RequestSerializerInterface $serializer = null,
|
13454 |
+
ResponseReaderInterface $reader = null
|
13455 |
+
) {
|
13456 |
+
$this->setRequestSerializer($serializer ?: new RequestSerializer());
|
13457 |
+
$this->setResponseReader($reader ?: new ResponseReader());
|
13458 |
+
}
|
13459 |
+
|
13460 |
+
/**
|
13461 |
+
* {@inheritdoc}
|
13462 |
+
*/
|
13463 |
+
public function write(CompositeConnectionInterface $connection, CommandInterface $command)
|
13464 |
+
{
|
13465 |
+
$connection->writeBuffer($this->serializer->serialize($command));
|
13466 |
+
}
|
13467 |
+
|
13468 |
+
/**
|
13469 |
+
* {@inheritdoc}
|
13470 |
+
*/
|
13471 |
+
public function read(CompositeConnectionInterface $connection)
|
13472 |
+
{
|
13473 |
+
return $this->reader->read($connection);
|
13474 |
+
}
|
13475 |
+
|
13476 |
+
/**
|
13477 |
+
* Sets the request serializer used by the protocol processor.
|
13478 |
+
*
|
13479 |
+
* @param RequestSerializerInterface $serializer Request serializer.
|
13480 |
+
*/
|
13481 |
+
public function setRequestSerializer(RequestSerializerInterface $serializer)
|
13482 |
+
{
|
13483 |
+
$this->serializer = $serializer;
|
13484 |
+
}
|
13485 |
+
|
13486 |
+
/**
|
13487 |
+
* Returns the request serializer used by the protocol processor.
|
13488 |
+
*
|
13489 |
+
* @return RequestSerializerInterface
|
13490 |
+
*/
|
13491 |
+
public function getRequestSerializer()
|
13492 |
+
{
|
13493 |
+
return $this->serializer;
|
13494 |
+
}
|
13495 |
+
|
13496 |
+
/**
|
13497 |
+
* Sets the response reader used by the protocol processor.
|
13498 |
+
*
|
13499 |
+
* @param ResponseReaderInterface $reader Response reader.
|
13500 |
+
*/
|
13501 |
+
public function setResponseReader(ResponseReaderInterface $reader)
|
13502 |
+
{
|
13503 |
+
$this->reader = $reader;
|
13504 |
+
}
|
13505 |
+
|
13506 |
+
/**
|
13507 |
+
* Returns the Response reader used by the protocol processor.
|
13508 |
+
*
|
13509 |
+
* @return ResponseReaderInterface
|
13510 |
+
*/
|
13511 |
+
public function getResponseReader()
|
13512 |
+
{
|
13513 |
+
return $this->reader;
|
13514 |
+
}
|
13515 |
+
}
|
13516 |
+
|
13517 |
+
/* --------------------------------------------------------------------------- */
|
13518 |
+
|
13519 |
+
namespace Predis\PubSub;
|
13520 |
+
|
13521 |
+
use Iterator;
|
13522 |
+
use Predis\ClientException;
|
13523 |
+
use Predis\ClientInterface;
|
13524 |
+
use Predis\Command\Command;
|
13525 |
+
use Predis\NotSupportedException;
|
13526 |
+
use Predis\Connection\AggregateConnectionInterface;
|
13527 |
+
use InvalidArgumentException;
|
13528 |
+
|
13529 |
+
/**
|
13530 |
+
* Base implementation of a PUB/SUB consumer abstraction based on PHP iterators.
|
13531 |
+
*
|
13532 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13533 |
+
*/
|
13534 |
+
abstract class AbstractConsumer implements Iterator
|
13535 |
+
{
|
13536 |
+
const SUBSCRIBE = 'subscribe';
|
13537 |
+
const UNSUBSCRIBE = 'unsubscribe';
|
13538 |
+
const PSUBSCRIBE = 'psubscribe';
|
13539 |
+
const PUNSUBSCRIBE = 'punsubscribe';
|
13540 |
+
const MESSAGE = 'message';
|
13541 |
+
const PMESSAGE = 'pmessage';
|
13542 |
+
const PONG = 'pong';
|
13543 |
+
|
13544 |
+
const STATUS_VALID = 1; // 0b0001
|
13545 |
+
const STATUS_SUBSCRIBED = 2; // 0b0010
|
13546 |
+
const STATUS_PSUBSCRIBED = 4; // 0b0100
|
13547 |
+
|
13548 |
+
private $position = null;
|
13549 |
+
private $statusFlags = self::STATUS_VALID;
|
13550 |
+
|
13551 |
+
/**
|
13552 |
+
* Automatically stops the consumer when the garbage collector kicks in.
|
13553 |
+
*/
|
13554 |
+
public function __destruct()
|
13555 |
+
{
|
13556 |
+
$this->stop(true);
|
13557 |
+
}
|
13558 |
+
|
13559 |
+
/**
|
13560 |
+
* Checks if the specified flag is valid based on the state of the consumer.
|
13561 |
+
*
|
13562 |
+
* @param int $value Flag.
|
13563 |
+
*
|
13564 |
+
* @return bool
|
13565 |
+
*/
|
13566 |
+
protected function isFlagSet($value)
|
13567 |
+
{
|
13568 |
+
return ($this->statusFlags & $value) === $value;
|
13569 |
+
}
|
13570 |
+
|
13571 |
+
/**
|
13572 |
+
* Subscribes to the specified channels.
|
13573 |
+
*
|
13574 |
+
* @param mixed $channel,... One or more channel names.
|
13575 |
+
*/
|
13576 |
+
public function subscribe($channel /*, ... */)
|
13577 |
+
{
|
13578 |
+
$this->writeRequest(self::SUBSCRIBE, func_get_args());
|
13579 |
+
$this->statusFlags |= self::STATUS_SUBSCRIBED;
|
13580 |
+
}
|
13581 |
+
|
13582 |
+
/**
|
13583 |
+
* Unsubscribes from the specified channels.
|
13584 |
+
*
|
13585 |
+
* @param string ... One or more channel names.
|
13586 |
+
*/
|
13587 |
+
public function unsubscribe(/* ... */)
|
13588 |
+
{
|
13589 |
+
$this->writeRequest(self::UNSUBSCRIBE, func_get_args());
|
13590 |
+
}
|
13591 |
+
|
13592 |
+
/**
|
13593 |
+
* Subscribes to the specified channels using a pattern.
|
13594 |
+
*
|
13595 |
+
* @param mixed $pattern,... One or more channel name patterns.
|
13596 |
+
*/
|
13597 |
+
public function psubscribe($pattern /* ... */)
|
13598 |
+
{
|
13599 |
+
$this->writeRequest(self::PSUBSCRIBE, func_get_args());
|
13600 |
+
$this->statusFlags |= self::STATUS_PSUBSCRIBED;
|
13601 |
+
}
|
13602 |
+
|
13603 |
+
/**
|
13604 |
+
* Unsubscribes from the specified channels using a pattern.
|
13605 |
+
*
|
13606 |
+
* @param string ... One or more channel name patterns.
|
13607 |
+
*/
|
13608 |
+
public function punsubscribe(/* ... */)
|
13609 |
+
{
|
13610 |
+
$this->writeRequest(self::PUNSUBSCRIBE, func_get_args());
|
13611 |
+
}
|
13612 |
+
|
13613 |
+
/**
|
13614 |
+
* PING the server with an optional payload that will be echoed as a
|
13615 |
+
* PONG message in the pub/sub loop.
|
13616 |
+
*
|
13617 |
+
* @param string $payload Optional PING payload.
|
13618 |
+
*/
|
13619 |
+
public function ping($payload = null)
|
13620 |
+
{
|
13621 |
+
$this->writeRequest('PING', array($payload));
|
13622 |
+
}
|
13623 |
+
|
13624 |
+
/**
|
13625 |
+
* Closes the context by unsubscribing from all the subscribed channels. The
|
13626 |
+
* context can be forcefully closed by dropping the underlying connection.
|
13627 |
+
*
|
13628 |
+
* @param bool $drop Indicates if the context should be closed by dropping the connection.
|
13629 |
+
*
|
13630 |
+
* @return bool Returns false when there are no pending messages.
|
13631 |
+
*/
|
13632 |
+
public function stop($drop = false)
|
13633 |
+
{
|
13634 |
+
if (!$this->valid()) {
|
13635 |
+
return false;
|
13636 |
+
}
|
13637 |
+
|
13638 |
+
if ($drop) {
|
13639 |
+
$this->invalidate();
|
13640 |
+
$this->disconnect();
|
13641 |
+
} else {
|
13642 |
+
if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) {
|
13643 |
+
$this->unsubscribe();
|
13644 |
+
}
|
13645 |
+
if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) {
|
13646 |
+
$this->punsubscribe();
|
13647 |
+
}
|
13648 |
+
}
|
13649 |
+
|
13650 |
+
return !$drop;
|
13651 |
+
}
|
13652 |
+
|
13653 |
+
/**
|
13654 |
+
* Closes the underlying connection when forcing a disconnection.
|
13655 |
+
*/
|
13656 |
+
abstract protected function disconnect();
|
13657 |
+
|
13658 |
+
/**
|
13659 |
+
* Writes a Redis command on the underlying connection.
|
13660 |
+
*
|
13661 |
+
* @param string $method Command ID.
|
13662 |
+
* @param array $arguments Arguments for the command.
|
13663 |
+
*/
|
13664 |
+
abstract protected function writeRequest($method, $arguments);
|
13665 |
+
|
13666 |
+
/**
|
13667 |
+
* {@inheritdoc}
|
13668 |
+
*/
|
13669 |
+
public function rewind()
|
13670 |
+
{
|
13671 |
+
// NOOP
|
13672 |
+
}
|
13673 |
+
|
13674 |
+
/**
|
13675 |
+
* Returns the last message payload retrieved from the server and generated
|
13676 |
+
* by one of the active subscriptions.
|
13677 |
+
*
|
13678 |
+
* @return array
|
13679 |
+
*/
|
13680 |
+
public function current()
|
13681 |
+
{
|
13682 |
+
return $this->getValue();
|
13683 |
+
}
|
13684 |
+
|
13685 |
+
/**
|
13686 |
+
* {@inheritdoc}
|
13687 |
+
*/
|
13688 |
+
public function key()
|
13689 |
+
{
|
13690 |
+
return $this->position;
|
13691 |
+
}
|
13692 |
+
|
13693 |
+
/**
|
13694 |
+
* {@inheritdoc}
|
13695 |
+
*/
|
13696 |
+
public function next()
|
13697 |
+
{
|
13698 |
+
if ($this->valid()) {
|
13699 |
+
$this->position++;
|
13700 |
+
}
|
13701 |
+
|
13702 |
+
return $this->position;
|
13703 |
+
}
|
13704 |
+
|
13705 |
+
/**
|
13706 |
+
* Checks if the the consumer is still in a valid state to continue.
|
13707 |
+
*
|
13708 |
+
* @return bool
|
13709 |
+
*/
|
13710 |
+
public function valid()
|
13711 |
+
{
|
13712 |
+
$isValid = $this->isFlagSet(self::STATUS_VALID);
|
13713 |
+
$subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
|
13714 |
+
$hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
|
13715 |
+
|
13716 |
+
return $isValid && $hasSubscriptions;
|
13717 |
+
}
|
13718 |
+
|
13719 |
+
/**
|
13720 |
+
* Resets the state of the consumer.
|
13721 |
+
*/
|
13722 |
+
protected function invalidate()
|
13723 |
+
{
|
13724 |
+
$this->statusFlags = 0; // 0b0000;
|
13725 |
+
}
|
13726 |
+
|
13727 |
+
/**
|
13728 |
+
* Waits for a new message from the server generated by one of the active
|
13729 |
+
* subscriptions and returns it when available.
|
13730 |
+
*
|
13731 |
+
* @return array
|
13732 |
+
*/
|
13733 |
+
abstract protected function getValue();
|
13734 |
+
}
|
13735 |
+
|
13736 |
+
/**
|
13737 |
+
* Method-dispatcher loop built around the client-side abstraction of a Redis
|
13738 |
+
* PUB / SUB context.
|
13739 |
+
*
|
13740 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13741 |
+
*/
|
13742 |
+
class DispatcherLoop
|
13743 |
+
{
|
13744 |
+
private $pubsub;
|
13745 |
+
|
13746 |
+
protected $callbacks;
|
13747 |
+
protected $defaultCallback;
|
13748 |
+
protected $subscriptionCallback;
|
13749 |
+
|
13750 |
+
/**
|
13751 |
+
* @param Consumer $pubsub PubSub consumer instance used by the loop.
|
13752 |
+
*/
|
13753 |
+
public function __construct(Consumer $pubsub)
|
13754 |
+
{
|
13755 |
+
$this->callbacks = array();
|
13756 |
+
$this->pubsub = $pubsub;
|
13757 |
+
}
|
13758 |
+
|
13759 |
+
/**
|
13760 |
+
* Checks if the passed argument is a valid callback.
|
13761 |
+
*
|
13762 |
+
* @param mixed $callable A callback.
|
13763 |
+
*
|
13764 |
+
* @throws \InvalidArgumentException
|
13765 |
+
*/
|
13766 |
+
protected function assertCallback($callable)
|
13767 |
+
{
|
13768 |
+
if (!is_callable($callable)) {
|
13769 |
+
throw new InvalidArgumentException('The given argument must be a callable object.');
|
13770 |
+
}
|
13771 |
+
}
|
13772 |
+
|
13773 |
+
/**
|
13774 |
+
* Returns the underlying PUB / SUB context.
|
13775 |
+
*
|
13776 |
+
* @return Consumer
|
13777 |
+
*/
|
13778 |
+
public function getPubSubConsumer()
|
13779 |
+
{
|
13780 |
+
return $this->pubsub;
|
13781 |
+
}
|
13782 |
+
|
13783 |
+
/**
|
13784 |
+
* Sets a callback that gets invoked upon new subscriptions.
|
13785 |
+
*
|
13786 |
+
* @param mixed $callable A callback.
|
13787 |
+
*/
|
13788 |
+
public function subscriptionCallback($callable = null)
|
13789 |
+
{
|
13790 |
+
if (isset($callable)) {
|
13791 |
+
$this->assertCallback($callable);
|
13792 |
+
}
|
13793 |
+
|
13794 |
+
$this->subscriptionCallback = $callable;
|
13795 |
+
}
|
13796 |
+
|
13797 |
+
/**
|
13798 |
+
* Sets a callback that gets invoked when a message is received on a
|
13799 |
+
* channel that does not have an associated callback.
|
13800 |
+
*
|
13801 |
+
* @param mixed $callable A callback.
|
13802 |
+
*/
|
13803 |
+
public function defaultCallback($callable = null)
|
13804 |
+
{
|
13805 |
+
if (isset($callable)) {
|
13806 |
+
$this->assertCallback($callable);
|
13807 |
+
}
|
13808 |
+
|
13809 |
+
$this->subscriptionCallback = $callable;
|
13810 |
+
}
|
13811 |
+
|
13812 |
+
/**
|
13813 |
+
* Binds a callback to a channel.
|
13814 |
+
*
|
13815 |
+
* @param string $channel Channel name.
|
13816 |
+
* @param Callable $callback A callback.
|
13817 |
+
*/
|
13818 |
+
public function attachCallback($channel, $callback)
|
13819 |
+
{
|
13820 |
+
$callbackName = $this->getPrefixKeys() . $channel;
|
13821 |
+
|
13822 |
+
$this->assertCallback($callback);
|
13823 |
+
$this->callbacks[$callbackName] = $callback;
|
13824 |
+
$this->pubsub->subscribe($channel);
|
13825 |
+
}
|
13826 |
+
|
13827 |
+
/**
|
13828 |
+
* Stops listening to a channel and removes the associated callback.
|
13829 |
+
*
|
13830 |
+
* @param string $channel Redis channel.
|
13831 |
+
*/
|
13832 |
+
public function detachCallback($channel)
|
13833 |
+
{
|
13834 |
+
$callbackName = $this->getPrefixKeys() . $channel;
|
13835 |
+
|
13836 |
+
if (isset($this->callbacks[$callbackName])) {
|
13837 |
+
unset($this->callbacks[$callbackName]);
|
13838 |
+
$this->pubsub->unsubscribe($channel);
|
13839 |
+
}
|
13840 |
+
}
|
13841 |
+
|
13842 |
+
/**
|
13843 |
+
* Starts the dispatcher loop.
|
13844 |
+
*/
|
13845 |
+
public function run()
|
13846 |
+
{
|
13847 |
+
foreach ($this->pubsub as $message) {
|
13848 |
+
$kind = $message->kind;
|
13849 |
+
|
13850 |
+
if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) {
|
13851 |
+
if (isset($this->subscriptionCallback)) {
|
13852 |
+
$callback = $this->subscriptionCallback;
|
13853 |
+
call_user_func($callback, $message);
|
13854 |
+
}
|
13855 |
+
|
13856 |
+
continue;
|
13857 |
+
}
|
13858 |
+
|
13859 |
+
if (isset($this->callbacks[$message->channel])) {
|
13860 |
+
$callback = $this->callbacks[$message->channel];
|
13861 |
+
call_user_func($callback, $message->payload);
|
13862 |
+
} elseif (isset($this->defaultCallback)) {
|
13863 |
+
$callback = $this->defaultCallback;
|
13864 |
+
call_user_func($callback, $message);
|
13865 |
+
}
|
13866 |
+
}
|
13867 |
+
}
|
13868 |
+
|
13869 |
+
/**
|
13870 |
+
* Terminates the dispatcher loop.
|
13871 |
+
*/
|
13872 |
+
public function stop()
|
13873 |
+
{
|
13874 |
+
$this->pubsub->stop();
|
13875 |
+
}
|
13876 |
+
|
13877 |
+
/**
|
13878 |
+
* Return the prefix used for keys
|
13879 |
+
*
|
13880 |
+
* @return string
|
13881 |
+
*/
|
13882 |
+
protected function getPrefixKeys()
|
13883 |
+
{
|
13884 |
+
$options = $this->pubsub->getClient()->getOptions();
|
13885 |
+
|
13886 |
+
if (isset($options->prefix)) {
|
13887 |
+
return $options->prefix->getPrefix();
|
13888 |
+
}
|
13889 |
+
|
13890 |
+
return '';
|
13891 |
+
}
|
13892 |
+
}
|
13893 |
+
|
13894 |
+
/**
|
13895 |
+
* PUB/SUB consumer abstraction.
|
13896 |
+
*
|
13897 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
13898 |
+
*/
|
13899 |
+
class Consumer extends AbstractConsumer
|
13900 |
+
{
|
13901 |
+
private $client;
|
13902 |
+
private $options;
|
13903 |
+
|
13904 |
+
/**
|
13905 |
+
* @param ClientInterface $client Client instance used by the consumer.
|
13906 |
+
* @param array $options Options for the consumer initialization.
|
13907 |
+
*/
|
13908 |
+
public function __construct(ClientInterface $client, array $options = null)
|
13909 |
+
{
|
13910 |
+
$this->checkCapabilities($client);
|
13911 |
+
|
13912 |
+
$this->options = $options ?: array();
|
13913 |
+
$this->client = $client;
|
13914 |
+
|
13915 |
+
$this->genericSubscribeInit('subscribe');
|
13916 |
+
$this->genericSubscribeInit('psubscribe');
|
13917 |
+
}
|
13918 |
+
|
13919 |
+
/**
|
13920 |
+
* Returns the underlying client instance used by the pub/sub iterator.
|
13921 |
+
*
|
13922 |
+
* @return ClientInterface
|
13923 |
+
*/
|
13924 |
+
public function getClient()
|
13925 |
+
{
|
13926 |
+
return $this->client;
|
13927 |
+
}
|
13928 |
+
|
13929 |
+
/**
|
13930 |
+
* Checks if the client instance satisfies the required conditions needed to
|
13931 |
+
* initialize a PUB/SUB consumer.
|
13932 |
+
*
|
13933 |
+
* @param ClientInterface $client Client instance used by the consumer.
|
13934 |
+
*
|
13935 |
+
* @throws NotSupportedException
|
13936 |
+
*/
|
13937 |
+
private function checkCapabilities(ClientInterface $client)
|
13938 |
+
{
|
13939 |
+
if ($client->getConnection() instanceof AggregateConnectionInterface) {
|
13940 |
+
throw new NotSupportedException(
|
13941 |
+
'Cannot initialize a PUB/SUB consumer over aggregate connections.'
|
13942 |
+
);
|
13943 |
+
}
|
13944 |
+
|
13945 |
+
$commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
|
13946 |
+
|
13947 |
+
if ($client->getProfile()->supportsCommands($commands) === false) {
|
13948 |
+
throw new NotSupportedException(
|
13949 |
+
'The current profile does not support PUB/SUB related commands.'
|
13950 |
+
);
|
13951 |
+
}
|
13952 |
+
}
|
13953 |
+
|
13954 |
+
/**
|
13955 |
+
* This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
|
13956 |
+
*
|
13957 |
+
* @param string $subscribeAction Type of subscription.
|
13958 |
+
*/
|
13959 |
+
private function genericSubscribeInit($subscribeAction)
|
13960 |
+
{
|
13961 |
+
if (isset($this->options[$subscribeAction])) {
|
13962 |
+
$this->$subscribeAction($this->options[$subscribeAction]);
|
13963 |
+
}
|
13964 |
+
}
|
13965 |
+
|
13966 |
+
/**
|
13967 |
+
* {@inheritdoc}
|
13968 |
+
*/
|
13969 |
+
protected function writeRequest($method, $arguments)
|
13970 |
+
{
|
13971 |
+
$this->client->getConnection()->writeRequest(
|
13972 |
+
$this->client->createCommand($method,
|
13973 |
+
Command::normalizeArguments($arguments)
|
13974 |
+
)
|
13975 |
+
);
|
13976 |
+
}
|
13977 |
+
|
13978 |
+
/**
|
13979 |
+
* {@inheritdoc}
|
13980 |
+
*/
|
13981 |
+
protected function disconnect()
|
13982 |
+
{
|
13983 |
+
$this->client->disconnect();
|
13984 |
+
}
|
13985 |
+
|
13986 |
+
/**
|
13987 |
+
* {@inheritdoc}
|
13988 |
+
*/
|
13989 |
+
protected function getValue()
|
13990 |
+
{
|
13991 |
+
$response = $this->client->getConnection()->read();
|
13992 |
+
|
13993 |
+
switch ($response[0]) {
|
13994 |
+
case self::SUBSCRIBE:
|
13995 |
+
case self::UNSUBSCRIBE:
|
13996 |
+
case self::PSUBSCRIBE:
|
13997 |
+
case self::PUNSUBSCRIBE:
|
13998 |
+
if ($response[2] === 0) {
|
13999 |
+
$this->invalidate();
|
14000 |
+
}
|
14001 |
+
// The missing break here is intentional as we must process
|
14002 |
+
// subscriptions and unsubscriptions as standard messages.
|
14003 |
+
// no break
|
14004 |
+
|
14005 |
+
case self::MESSAGE:
|
14006 |
+
return (object) array(
|
14007 |
+
'kind' => $response[0],
|
14008 |
+
'channel' => $response[1],
|
14009 |
+
'payload' => $response[2],
|
14010 |
+
);
|
14011 |
+
|
14012 |
+
case self::PMESSAGE:
|
14013 |
+
return (object) array(
|
14014 |
+
'kind' => $response[0],
|
14015 |
+
'pattern' => $response[1],
|
14016 |
+
'channel' => $response[2],
|
14017 |
+
'payload' => $response[3],
|
14018 |
+
);
|
14019 |
+
|
14020 |
+
case self::PONG:
|
14021 |
+
return (object) array(
|
14022 |
+
'kind' => $response[0],
|
14023 |
+
'payload' => $response[1],
|
14024 |
+
);
|
14025 |
+
|
14026 |
+
default:
|
14027 |
+
throw new ClientException(
|
14028 |
+
"Unknown message type '{$response[0]}' received in the PUB/SUB context."
|
14029 |
+
);
|
14030 |
+
}
|
14031 |
+
}
|
14032 |
+
}
|
14033 |
+
|
14034 |
+
/* --------------------------------------------------------------------------- */
|
14035 |
+
|
14036 |
+
namespace Predis\Transaction;
|
14037 |
+
|
14038 |
+
use Predis\PredisException;
|
14039 |
+
use Exception;
|
14040 |
+
use InvalidArgumentException;
|
14041 |
+
use SplQueue;
|
14042 |
+
use Predis\ClientContextInterface;
|
14043 |
+
use Predis\ClientException;
|
14044 |
+
use Predis\ClientInterface;
|
14045 |
+
use Predis\CommunicationException;
|
14046 |
+
use Predis\NotSupportedException;
|
14047 |
+
use Predis\Response\ErrorInterface as ErrorResponseInterface;
|
14048 |
+
use Predis\Response\ServerException;
|
14049 |
+
use Predis\Response\Status as StatusResponse;
|
14050 |
+
use Predis\Command\CommandInterface;
|
14051 |
+
use Predis\Connection\AggregateConnectionInterface;
|
14052 |
+
use Predis\Protocol\ProtocolException;
|
14053 |
+
|
14054 |
+
/**
|
14055 |
+
* Utility class used to track the state of a MULTI / EXEC transaction.
|
14056 |
+
*
|
14057 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14058 |
+
*/
|
14059 |
+
class MultiExecState
|
14060 |
+
{
|
14061 |
+
const INITIALIZED = 1; // 0b00001
|
14062 |
+
const INSIDEBLOCK = 2; // 0b00010
|
14063 |
+
const DISCARDED = 4; // 0b00100
|
14064 |
+
const CAS = 8; // 0b01000
|
14065 |
+
const WATCH = 16; // 0b10000
|
14066 |
+
|
14067 |
+
private $flags;
|
14068 |
+
|
14069 |
+
/**
|
14070 |
+
*
|
14071 |
+
*/
|
14072 |
+
public function __construct()
|
14073 |
+
{
|
14074 |
+
$this->flags = 0;
|
14075 |
+
}
|
14076 |
+
|
14077 |
+
/**
|
14078 |
+
* Sets the internal state flags.
|
14079 |
+
*
|
14080 |
+
* @param int $flags Set of flags
|
14081 |
+
*/
|
14082 |
+
public function set($flags)
|
14083 |
+
{
|
14084 |
+
$this->flags = $flags;
|
14085 |
+
}
|
14086 |
+
|
14087 |
+
/**
|
14088 |
+
* Gets the internal state flags.
|
14089 |
+
*
|
14090 |
+
* @return int
|
14091 |
+
*/
|
14092 |
+
public function get()
|
14093 |
+
{
|
14094 |
+
return $this->flags;
|
14095 |
+
}
|
14096 |
+
|
14097 |
+
/**
|
14098 |
+
* Sets one or more flags.
|
14099 |
+
*
|
14100 |
+
* @param int $flags Set of flags
|
14101 |
+
*/
|
14102 |
+
public function flag($flags)
|
14103 |
+
{
|
14104 |
+
$this->flags |= $flags;
|
14105 |
+
}
|
14106 |
+
|
14107 |
+
/**
|
14108 |
+
* Resets one or more flags.
|
14109 |
+
*
|
14110 |
+
* @param int $flags Set of flags
|
14111 |
+
*/
|
14112 |
+
public function unflag($flags)
|
14113 |
+
{
|
14114 |
+
$this->flags &= ~$flags;
|
14115 |
+
}
|
14116 |
+
|
14117 |
+
/**
|
14118 |
+
* Returns if the specified flag or set of flags is set.
|
14119 |
+
*
|
14120 |
+
* @param int $flags Flag
|
14121 |
+
*
|
14122 |
+
* @return bool
|
14123 |
+
*/
|
14124 |
+
public function check($flags)
|
14125 |
+
{
|
14126 |
+
return ($this->flags & $flags) === $flags;
|
14127 |
+
}
|
14128 |
+
|
14129 |
+
/**
|
14130 |
+
* Resets the state of a transaction.
|
14131 |
+
*/
|
14132 |
+
public function reset()
|
14133 |
+
{
|
14134 |
+
$this->flags = 0;
|
14135 |
+
}
|
14136 |
+
|
14137 |
+
/**
|
14138 |
+
* Returns the state of the RESET flag.
|
14139 |
+
*
|
14140 |
+
* @return bool
|
14141 |
+
*/
|
14142 |
+
public function isReset()
|
14143 |
+
{
|
14144 |
+
return $this->flags === 0;
|
14145 |
+
}
|
14146 |
+
|
14147 |
+
/**
|
14148 |
+
* Returns the state of the INITIALIZED flag.
|
14149 |
+
*
|
14150 |
+
* @return bool
|
14151 |
+
*/
|
14152 |
+
public function isInitialized()
|
14153 |
+
{
|
14154 |
+
return $this->check(self::INITIALIZED);
|
14155 |
+
}
|
14156 |
+
|
14157 |
+
/**
|
14158 |
+
* Returns the state of the INSIDEBLOCK flag.
|
14159 |
+
*
|
14160 |
+
* @return bool
|
14161 |
+
*/
|
14162 |
+
public function isExecuting()
|
14163 |
+
{
|
14164 |
+
return $this->check(self::INSIDEBLOCK);
|
14165 |
+
}
|
14166 |
+
|
14167 |
+
/**
|
14168 |
+
* Returns the state of the CAS flag.
|
14169 |
+
*
|
14170 |
+
* @return bool
|
14171 |
+
*/
|
14172 |
+
public function isCAS()
|
14173 |
+
{
|
14174 |
+
return $this->check(self::CAS);
|
14175 |
+
}
|
14176 |
+
|
14177 |
+
/**
|
14178 |
+
* Returns if WATCH is allowed in the current state.
|
14179 |
+
*
|
14180 |
+
* @return bool
|
14181 |
+
*/
|
14182 |
+
public function isWatchAllowed()
|
14183 |
+
{
|
14184 |
+
return $this->check(self::INITIALIZED) && !$this->check(self::CAS);
|
14185 |
+
}
|
14186 |
+
|
14187 |
+
/**
|
14188 |
+
* Returns the state of the WATCH flag.
|
14189 |
+
*
|
14190 |
+
* @return bool
|
14191 |
+
*/
|
14192 |
+
public function isWatching()
|
14193 |
+
{
|
14194 |
+
return $this->check(self::WATCH);
|
14195 |
+
}
|
14196 |
+
|
14197 |
+
/**
|
14198 |
+
* Returns the state of the DISCARDED flag.
|
14199 |
+
*
|
14200 |
+
* @return bool
|
14201 |
+
*/
|
14202 |
+
public function isDiscarded()
|
14203 |
+
{
|
14204 |
+
return $this->check(self::DISCARDED);
|
14205 |
+
}
|
14206 |
+
}
|
14207 |
+
|
14208 |
+
/**
|
14209 |
+
* Client-side abstraction of a Redis transaction based on MULTI / EXEC.
|
14210 |
+
*
|
14211 |
+
* {@inheritdoc}
|
14212 |
+
*
|
14213 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14214 |
+
*/
|
14215 |
+
class MultiExec implements ClientContextInterface
|
14216 |
+
{
|
14217 |
+
private $state;
|
14218 |
+
|
14219 |
+
protected $client;
|
14220 |
+
protected $commands;
|
14221 |
+
protected $exceptions = true;
|
14222 |
+
protected $attempts = 0;
|
14223 |
+
protected $watchKeys = array();
|
14224 |
+
protected $modeCAS = false;
|
14225 |
+
|
14226 |
+
/**
|
14227 |
+
* @param ClientInterface $client Client instance used by the transaction.
|
14228 |
+
* @param array $options Initialization options.
|
14229 |
+
*/
|
14230 |
+
public function __construct(ClientInterface $client, array $options = null)
|
14231 |
+
{
|
14232 |
+
$this->assertClient($client);
|
14233 |
+
|
14234 |
+
$this->client = $client;
|
14235 |
+
$this->state = new MultiExecState();
|
14236 |
+
|
14237 |
+
$this->configure($client, $options ?: array());
|
14238 |
+
$this->reset();
|
14239 |
+
}
|
14240 |
+
|
14241 |
+
/**
|
14242 |
+
* Checks if the passed client instance satisfies the required conditions
|
14243 |
+
* needed to initialize the transaction object.
|
14244 |
+
*
|
14245 |
+
* @param ClientInterface $client Client instance used by the transaction object.
|
14246 |
+
*
|
14247 |
+
* @throws NotSupportedException
|
14248 |
+
*/
|
14249 |
+
private function assertClient(ClientInterface $client)
|
14250 |
+
{
|
14251 |
+
if ($client->getConnection() instanceof AggregateConnectionInterface) {
|
14252 |
+
throw new NotSupportedException(
|
14253 |
+
'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
|
14254 |
+
);
|
14255 |
+
}
|
14256 |
+
|
14257 |
+
if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) {
|
14258 |
+
throw new NotSupportedException(
|
14259 |
+
'The current profile does not support MULTI, EXEC and DISCARD.'
|
14260 |
+
);
|
14261 |
+
}
|
14262 |
+
}
|
14263 |
+
|
14264 |
+
/**
|
14265 |
+
* Configures the transaction using the provided options.
|
14266 |
+
*
|
14267 |
+
* @param ClientInterface $client Underlying client instance.
|
14268 |
+
* @param array $options Array of options for the transaction.
|
14269 |
+
**/
|
14270 |
+
protected function configure(ClientInterface $client, array $options)
|
14271 |
+
{
|
14272 |
+
if (isset($options['exceptions'])) {
|
14273 |
+
$this->exceptions = (bool) $options['exceptions'];
|
14274 |
+
} else {
|
14275 |
+
$this->exceptions = $client->getOptions()->exceptions;
|
14276 |
+
}
|
14277 |
+
|
14278 |
+
if (isset($options['cas'])) {
|
14279 |
+
$this->modeCAS = (bool) $options['cas'];
|
14280 |
+
}
|
14281 |
+
|
14282 |
+
if (isset($options['watch']) && $keys = $options['watch']) {
|
14283 |
+
$this->watchKeys = $keys;
|
14284 |
+
}
|
14285 |
+
|
14286 |
+
if (isset($options['retry'])) {
|
14287 |
+
$this->attempts = (int) $options['retry'];
|
14288 |
+
}
|
14289 |
+
}
|
14290 |
+
|
14291 |
+
/**
|
14292 |
+
* Resets the state of the transaction.
|
14293 |
+
*/
|
14294 |
+
protected function reset()
|
14295 |
+
{
|
14296 |
+
$this->state->reset();
|
14297 |
+
$this->commands = new SplQueue();
|
14298 |
+
}
|
14299 |
+
|
14300 |
+
/**
|
14301 |
+
* Initializes the transaction context.
|
14302 |
+
*/
|
14303 |
+
protected function initialize()
|
14304 |
+
{
|
14305 |
+
if ($this->state->isInitialized()) {
|
14306 |
+
return;
|
14307 |
+
}
|
14308 |
+
|
14309 |
+
if ($this->modeCAS) {
|
14310 |
+
$this->state->flag(MultiExecState::CAS);
|
14311 |
+
}
|
14312 |
+
|
14313 |
+
if ($this->watchKeys) {
|
14314 |
+
$this->watch($this->watchKeys);
|
14315 |
+
}
|
14316 |
+
|
14317 |
+
$cas = $this->state->isCAS();
|
14318 |
+
$discarded = $this->state->isDiscarded();
|
14319 |
+
|
14320 |
+
if (!$cas || ($cas && $discarded)) {
|
14321 |
+
$this->call('MULTI');
|
14322 |
+
|
14323 |
+
if ($discarded) {
|
14324 |
+
$this->state->unflag(MultiExecState::CAS);
|
14325 |
+
}
|
14326 |
+
}
|
14327 |
+
|
14328 |
+
$this->state->unflag(MultiExecState::DISCARDED);
|
14329 |
+
$this->state->flag(MultiExecState::INITIALIZED);
|
14330 |
+
}
|
14331 |
+
|
14332 |
+
/**
|
14333 |
+
* Dynamically invokes a Redis command with the specified arguments.
|
14334 |
+
*
|
14335 |
+
* @param string $method Command ID.
|
14336 |
+
* @param array $arguments Arguments for the command.
|
14337 |
+
*
|
14338 |
+
* @return mixed
|
14339 |
+
*/
|
14340 |
+
public function __call($method, $arguments)
|
14341 |
+
{
|
14342 |
+
return $this->executeCommand(
|
14343 |
+
$this->client->createCommand($method, $arguments)
|
14344 |
+
);
|
14345 |
+
}
|
14346 |
+
|
14347 |
+
/**
|
14348 |
+
* Executes a Redis command bypassing the transaction logic.
|
14349 |
+
*
|
14350 |
+
* @param string $commandID Command ID.
|
14351 |
+
* @param array $arguments Arguments for the command.
|
14352 |
+
*
|
14353 |
+
* @return mixed
|
14354 |
+
*
|
14355 |
+
* @throws ServerException
|
14356 |
+
*/
|
14357 |
+
protected function call($commandID, array $arguments = array())
|
14358 |
+
{
|
14359 |
+
$response = $this->client->executeCommand(
|
14360 |
+
$this->client->createCommand($commandID, $arguments)
|
14361 |
+
);
|
14362 |
+
|
14363 |
+
if ($response instanceof ErrorResponseInterface) {
|
14364 |
+
throw new ServerException($response->getMessage());
|
14365 |
+
}
|
14366 |
+
|
14367 |
+
return $response;
|
14368 |
+
}
|
14369 |
+
|
14370 |
+
/**
|
14371 |
+
* Executes the specified Redis command.
|
14372 |
+
*
|
14373 |
+
* @param CommandInterface $command Command instance.
|
14374 |
+
*
|
14375 |
+
* @return $this|mixed
|
14376 |
+
*
|
14377 |
+
* @throws AbortedMultiExecException
|
14378 |
+
* @throws CommunicationException
|
14379 |
+
*/
|
14380 |
+
public function executeCommand(CommandInterface $command)
|
14381 |
+
{
|
14382 |
+
$this->initialize();
|
14383 |
+
if ($this->state->isCAS()) {
|
14384 |
+
return $this->client->executeCommand($command);
|
14385 |
+
}
|
14386 |
+
|
14387 |
+
$response = $this->client->getConnection()->executeCommand($command);
|
14388 |
+
|
14389 |
+
if ($response instanceof StatusResponse && $response == 'QUEUED') {
|
14390 |
+
$this->commands->enqueue($command);
|
14391 |
+
} elseif ($response instanceof ErrorResponseInterface) {
|
14392 |
+
throw new AbortedMultiExecException($this, $response->getMessage());
|
14393 |
+
} else {
|
14394 |
+
$this->onProtocolError('The server did not return a +QUEUED status response.');
|
14395 |
+
}
|
14396 |
+
|
14397 |
+
return $this;
|
14398 |
+
}
|
14399 |
+
|
14400 |
+
/**
|
14401 |
+
* Executes WATCH against one or more keys.
|
14402 |
+
*
|
14403 |
+
* @param string|array $keys One or more keys.
|
14404 |
+
*
|
14405 |
+
* @return mixed
|
14406 |
+
*
|
14407 |
+
* @throws NotSupportedException
|
14408 |
+
* @throws ClientException
|
14409 |
+
*/
|
14410 |
+
public function watch($keys)
|
14411 |
+
{
|
14412 |
+
if (!$this->client->getProfile()->supportsCommand('WATCH')) {
|
14413 |
+
throw new NotSupportedException('WATCH is not supported by the current profile.');
|
14414 |
+
}
|
14415 |
+
|
14416 |
+
if ($this->state->isWatchAllowed()) {
|
14417 |
+
throw new ClientException('Sending WATCH after MULTI is not allowed.');
|
14418 |
+
}
|
14419 |
+
|
14420 |
+
$response = $this->call('WATCH', is_array($keys) ? $keys : array($keys));
|
14421 |
+
$this->state->flag(MultiExecState::WATCH);
|
14422 |
+
|
14423 |
+
return $response;
|
14424 |
+
}
|
14425 |
+
|
14426 |
+
/**
|
14427 |
+
* Finalizes the transaction by executing MULTI on the server.
|
14428 |
+
*
|
14429 |
+
* @return MultiExec
|
14430 |
+
*/
|
14431 |
+
public function multi()
|
14432 |
+
{
|
14433 |
+
if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) {
|
14434 |
+
$this->state->unflag(MultiExecState::CAS);
|
14435 |
+
$this->call('MULTI');
|
14436 |
+
} else {
|
14437 |
+
$this->initialize();
|
14438 |
+
}
|
14439 |
+
|
14440 |
+
return $this;
|
14441 |
+
}
|
14442 |
+
|
14443 |
+
/**
|
14444 |
+
* Executes UNWATCH.
|
14445 |
+
*
|
14446 |
+
* @return MultiExec
|
14447 |
+
*
|
14448 |
+
* @throws NotSupportedException
|
14449 |
+
*/
|
14450 |
+
public function unwatch()
|
14451 |
+
{
|
14452 |
+
if (!$this->client->getProfile()->supportsCommand('UNWATCH')) {
|
14453 |
+
throw new NotSupportedException(
|
14454 |
+
'UNWATCH is not supported by the current profile.'
|
14455 |
+
);
|
14456 |
+
}
|
14457 |
+
|
14458 |
+
$this->state->unflag(MultiExecState::WATCH);
|
14459 |
+
$this->__call('UNWATCH', array());
|
14460 |
+
|
14461 |
+
return $this;
|
14462 |
+
}
|
14463 |
+
|
14464 |
+
/**
|
14465 |
+
* Resets the transaction by UNWATCH-ing the keys that are being WATCHed and
|
14466 |
+
* DISCARD-ing pending commands that have been already sent to the server.
|
14467 |
+
*
|
14468 |
+
* @return MultiExec
|
14469 |
+
*/
|
14470 |
+
public function discard()
|
14471 |
+
{
|
14472 |
+
if ($this->state->isInitialized()) {
|
14473 |
+
$this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD');
|
14474 |
+
|
14475 |
+
$this->reset();
|
14476 |
+
$this->state->flag(MultiExecState::DISCARDED);
|
14477 |
+
}
|
14478 |
+
|
14479 |
+
return $this;
|
14480 |
+
}
|
14481 |
+
|
14482 |
+
/**
|
14483 |
+
* Executes the whole transaction.
|
14484 |
+
*
|
14485 |
+
* @return mixed
|
14486 |
+
*/
|
14487 |
+
public function exec()
|
14488 |
+
{
|
14489 |
+
return $this->execute();
|
14490 |
+
}
|
14491 |
+
|
14492 |
+
/**
|
14493 |
+
* Checks the state of the transaction before execution.
|
14494 |
+
*
|
14495 |
+
* @param mixed $callable Callback for execution.
|
14496 |
+
*
|
14497 |
+
* @throws InvalidArgumentException
|
14498 |
+
* @throws ClientException
|
14499 |
+
*/
|
14500 |
+
private function checkBeforeExecution($callable)
|
14501 |
+
{
|
14502 |
+
if ($this->state->isExecuting()) {
|
14503 |
+
throw new ClientException(
|
14504 |
+
'Cannot invoke "execute" or "exec" inside an active transaction context.'
|
14505 |
+
);
|
14506 |
+
}
|
14507 |
+
|
14508 |
+
if ($callable) {
|
14509 |
+
if (!is_callable($callable)) {
|
14510 |
+
throw new InvalidArgumentException('The argument must be a callable object.');
|
14511 |
+
}
|
14512 |
+
|
14513 |
+
if (!$this->commands->isEmpty()) {
|
14514 |
+
$this->discard();
|
14515 |
+
|
14516 |
+
throw new ClientException(
|
14517 |
+
'Cannot execute a transaction block after using fluent interface.'
|
14518 |
+
);
|
14519 |
+
}
|
14520 |
+
} elseif ($this->attempts) {
|
14521 |
+
$this->discard();
|
14522 |
+
|
14523 |
+
throw new ClientException(
|
14524 |
+
'Automatic retries are supported only when a callable block is provided.'
|
14525 |
+
);
|
14526 |
+
}
|
14527 |
+
}
|
14528 |
+
|
14529 |
+
/**
|
14530 |
+
* Handles the actual execution of the whole transaction.
|
14531 |
+
*
|
14532 |
+
* @param mixed $callable Optional callback for execution.
|
14533 |
+
*
|
14534 |
+
* @return array
|
14535 |
+
*
|
14536 |
+
* @throws CommunicationException
|
14537 |
+
* @throws AbortedMultiExecException
|
14538 |
+
* @throws ServerException
|
14539 |
+
*/
|
14540 |
+
public function execute($callable = null)
|
14541 |
+
{
|
14542 |
+
$this->checkBeforeExecution($callable);
|
14543 |
+
|
14544 |
+
$execResponse = null;
|
14545 |
+
$attempts = $this->attempts;
|
14546 |
+
|
14547 |
+
do {
|
14548 |
+
if ($callable) {
|
14549 |
+
$this->executeTransactionBlock($callable);
|
14550 |
+
}
|
14551 |
+
|
14552 |
+
if ($this->commands->isEmpty()) {
|
14553 |
+
if ($this->state->isWatching()) {
|
14554 |
+
$this->discard();
|
14555 |
+
}
|
14556 |
+
|
14557 |
+
return null;
|
14558 |
+
}
|
14559 |
+
|
14560 |
+
$execResponse = $this->call('EXEC');
|
14561 |
+
|
14562 |
+
if ($execResponse === null) {
|
14563 |
+
if ($attempts === 0) {
|
14564 |
+
throw new AbortedMultiExecException(
|
14565 |
+
$this, 'The current transaction has been aborted by the server.'
|
14566 |
+
);
|
14567 |
+
}
|
14568 |
+
|
14569 |
+
$this->reset();
|
14570 |
+
|
14571 |
+
continue;
|
14572 |
+
}
|
14573 |
+
|
14574 |
+
break;
|
14575 |
+
} while ($attempts-- > 0);
|
14576 |
+
|
14577 |
+
$response = array();
|
14578 |
+
$commands = $this->commands;
|
14579 |
+
$size = count($execResponse);
|
14580 |
+
|
14581 |
+
if ($size !== count($commands)) {
|
14582 |
+
$this->onProtocolError('EXEC returned an unexpected number of response items.');
|
14583 |
+
}
|
14584 |
+
|
14585 |
+
for ($i = 0; $i < $size; $i++) {
|
14586 |
+
$cmdResponse = $execResponse[$i];
|
14587 |
+
|
14588 |
+
if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) {
|
14589 |
+
throw new ServerException($cmdResponse->getMessage());
|
14590 |
+
}
|
14591 |
+
|
14592 |
+
$response[$i] = $commands->dequeue()->parseResponse($cmdResponse);
|
14593 |
+
}
|
14594 |
+
|
14595 |
+
return $response;
|
14596 |
+
}
|
14597 |
+
|
14598 |
+
/**
|
14599 |
+
* Passes the current transaction object to a callable block for execution.
|
14600 |
+
*
|
14601 |
+
* @param mixed $callable Callback.
|
14602 |
+
*
|
14603 |
+
* @throws CommunicationException
|
14604 |
+
* @throws ServerException
|
14605 |
+
*/
|
14606 |
+
protected function executeTransactionBlock($callable)
|
14607 |
+
{
|
14608 |
+
$exception = null;
|
14609 |
+
$this->state->flag(MultiExecState::INSIDEBLOCK);
|
14610 |
+
|
14611 |
+
try {
|
14612 |
+
call_user_func($callable, $this);
|
14613 |
+
} catch (CommunicationException $exception) {
|
14614 |
+
// NOOP
|
14615 |
+
} catch (ServerException $exception) {
|
14616 |
+
// NOOP
|
14617 |
+
} catch (Exception $exception) {
|
14618 |
+
$this->discard();
|
14619 |
+
}
|
14620 |
+
|
14621 |
+
$this->state->unflag(MultiExecState::INSIDEBLOCK);
|
14622 |
+
|
14623 |
+
if ($exception) {
|
14624 |
+
throw $exception;
|
14625 |
+
}
|
14626 |
+
}
|
14627 |
+
|
14628 |
+
/**
|
14629 |
+
* Helper method for protocol errors encountered inside the transaction.
|
14630 |
+
*
|
14631 |
+
* @param string $message Error message.
|
14632 |
+
*/
|
14633 |
+
private function onProtocolError($message)
|
14634 |
+
{
|
14635 |
+
// Since a MULTI/EXEC block cannot be initialized when using aggregate
|
14636 |
+
// connections we can safely assume that Predis\Client::getConnection()
|
14637 |
+
// will return a Predis\Connection\NodeConnectionInterface instance.
|
14638 |
+
CommunicationException::handle(new ProtocolException(
|
14639 |
+
$this->client->getConnection(), $message
|
14640 |
+
));
|
14641 |
+
}
|
14642 |
+
}
|
14643 |
+
|
14644 |
+
/**
|
14645 |
+
* Exception class that identifies a MULTI / EXEC transaction aborted by Redis.
|
14646 |
+
*
|
14647 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14648 |
+
*/
|
14649 |
+
class AbortedMultiExecException extends PredisException
|
14650 |
+
{
|
14651 |
+
private $transaction;
|
14652 |
+
|
14653 |
+
/**
|
14654 |
+
* @param MultiExec $transaction Transaction that generated the exception.
|
14655 |
+
* @param string $message Error message.
|
14656 |
+
* @param int $code Error code.
|
14657 |
+
*/
|
14658 |
+
public function __construct(MultiExec $transaction, $message, $code = null)
|
14659 |
+
{
|
14660 |
+
parent::__construct($message, $code);
|
14661 |
+
$this->transaction = $transaction;
|
14662 |
+
}
|
14663 |
+
|
14664 |
+
/**
|
14665 |
+
* Returns the transaction that generated the exception.
|
14666 |
+
*
|
14667 |
+
* @return MultiExec
|
14668 |
+
*/
|
14669 |
+
public function getTransaction()
|
14670 |
+
{
|
14671 |
+
return $this->transaction;
|
14672 |
+
}
|
14673 |
+
}
|
14674 |
+
|
14675 |
+
/* --------------------------------------------------------------------------- */
|
14676 |
+
|
14677 |
+
namespace Predis\Session;
|
14678 |
+
|
14679 |
+
use SessionHandlerInterface;
|
14680 |
+
use Predis\ClientInterface;
|
14681 |
+
|
14682 |
+
/**
|
14683 |
+
* Session handler class that relies on Predis\Client to store PHP's sessions
|
14684 |
+
* data into one or multiple Redis servers.
|
14685 |
+
*
|
14686 |
+
* This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3
|
14687 |
+
* provided that a polyfill for `SessionHandlerInterface` is defined by either
|
14688 |
+
* you or an external package such as `symfony/http-foundation`.
|
14689 |
+
*
|
14690 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14691 |
+
*/
|
14692 |
+
class Handler implements SessionHandlerInterface
|
14693 |
+
{
|
14694 |
+
protected $client;
|
14695 |
+
protected $ttl;
|
14696 |
+
|
14697 |
+
/**
|
14698 |
+
* @param ClientInterface $client Fully initialized client instance.
|
14699 |
+
* @param array $options Session handler options.
|
14700 |
+
*/
|
14701 |
+
public function __construct(ClientInterface $client, array $options = array())
|
14702 |
+
{
|
14703 |
+
$this->client = $client;
|
14704 |
+
|
14705 |
+
if (isset($options['gc_maxlifetime'])) {
|
14706 |
+
$this->ttl = (int) $options['gc_maxlifetime'];
|
14707 |
+
} else {
|
14708 |
+
$this->ttl = ini_get('session.gc_maxlifetime');
|
14709 |
+
}
|
14710 |
+
}
|
14711 |
+
|
14712 |
+
/**
|
14713 |
+
* Registers this instance as the current session handler.
|
14714 |
+
*/
|
14715 |
+
public function register()
|
14716 |
+
{
|
14717 |
+
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
14718 |
+
session_set_save_handler($this, true);
|
14719 |
+
} else {
|
14720 |
+
session_set_save_handler(
|
14721 |
+
array($this, 'open'),
|
14722 |
+
array($this, 'close'),
|
14723 |
+
array($this, 'read'),
|
14724 |
+
array($this, 'write'),
|
14725 |
+
array($this, 'destroy'),
|
14726 |
+
array($this, 'gc')
|
14727 |
+
);
|
14728 |
+
}
|
14729 |
+
}
|
14730 |
+
|
14731 |
+
/**
|
14732 |
+
* {@inheritdoc}
|
14733 |
+
*/
|
14734 |
+
public function open($save_path, $session_id)
|
14735 |
+
{
|
14736 |
+
// NOOP
|
14737 |
+
return true;
|
14738 |
+
}
|
14739 |
+
|
14740 |
+
/**
|
14741 |
+
* {@inheritdoc}
|
14742 |
+
*/
|
14743 |
+
public function close()
|
14744 |
+
{
|
14745 |
+
// NOOP
|
14746 |
+
return true;
|
14747 |
+
}
|
14748 |
+
|
14749 |
+
/**
|
14750 |
+
* {@inheritdoc}
|
14751 |
+
*/
|
14752 |
+
public function gc($maxlifetime)
|
14753 |
+
{
|
14754 |
+
// NOOP
|
14755 |
+
return true;
|
14756 |
+
}
|
14757 |
+
|
14758 |
+
/**
|
14759 |
+
* {@inheritdoc}
|
14760 |
+
*/
|
14761 |
+
public function read($session_id)
|
14762 |
+
{
|
14763 |
+
if ($data = $this->client->get($session_id)) {
|
14764 |
+
return $data;
|
14765 |
+
}
|
14766 |
+
|
14767 |
+
return '';
|
14768 |
+
}
|
14769 |
+
/**
|
14770 |
+
* {@inheritdoc}
|
14771 |
+
*/
|
14772 |
+
public function write($session_id, $session_data)
|
14773 |
+
{
|
14774 |
+
$this->client->setex($session_id, $this->ttl, $session_data);
|
14775 |
+
|
14776 |
+
return true;
|
14777 |
+
}
|
14778 |
+
|
14779 |
+
/**
|
14780 |
+
* {@inheritdoc}
|
14781 |
+
*/
|
14782 |
+
public function destroy($session_id)
|
14783 |
+
{
|
14784 |
+
$this->client->del($session_id);
|
14785 |
+
|
14786 |
+
return true;
|
14787 |
+
}
|
14788 |
+
|
14789 |
+
/**
|
14790 |
+
* Returns the underlying client instance.
|
14791 |
+
*
|
14792 |
+
* @return ClientInterface
|
14793 |
+
*/
|
14794 |
+
public function getClient()
|
14795 |
+
{
|
14796 |
+
return $this->client;
|
14797 |
+
}
|
14798 |
+
|
14799 |
+
/**
|
14800 |
+
* Returns the session max lifetime value.
|
14801 |
+
*
|
14802 |
+
* @return int
|
14803 |
+
*/
|
14804 |
+
public function getMaxLifeTime()
|
14805 |
+
{
|
14806 |
+
return $this->ttl;
|
14807 |
+
}
|
14808 |
+
}
|
14809 |
+
|
14810 |
+
/* --------------------------------------------------------------------------- */
|
14811 |
+
|
14812 |
+
namespace Predis\Monitor;
|
14813 |
+
|
14814 |
+
use Iterator;
|
14815 |
+
use Predis\ClientInterface;
|
14816 |
+
use Predis\NotSupportedException;
|
14817 |
+
use Predis\Connection\AggregateConnectionInterface;
|
14818 |
+
|
14819 |
+
/**
|
14820 |
+
* Redis MONITOR consumer.
|
14821 |
+
*
|
14822 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14823 |
+
*/
|
14824 |
+
class Consumer implements Iterator
|
14825 |
+
{
|
14826 |
+
private $client;
|
14827 |
+
private $valid;
|
14828 |
+
private $position;
|
14829 |
+
|
14830 |
+
/**
|
14831 |
+
* @param ClientInterface $client Client instance used by the consumer.
|
14832 |
+
*/
|
14833 |
+
public function __construct(ClientInterface $client)
|
14834 |
+
{
|
14835 |
+
$this->assertClient($client);
|
14836 |
+
|
14837 |
+
$this->client = $client;
|
14838 |
+
|
14839 |
+
$this->start();
|
14840 |
+
}
|
14841 |
+
|
14842 |
+
/**
|
14843 |
+
* Automatically stops the consumer when the garbage collector kicks in.
|
14844 |
+
*/
|
14845 |
+
public function __destruct()
|
14846 |
+
{
|
14847 |
+
$this->stop();
|
14848 |
+
}
|
14849 |
+
|
14850 |
+
/**
|
14851 |
+
* Checks if the passed client instance satisfies the required conditions
|
14852 |
+
* needed to initialize a monitor consumer.
|
14853 |
+
*
|
14854 |
+
* @param ClientInterface $client Client instance used by the consumer.
|
14855 |
+
*
|
14856 |
+
* @throws NotSupportedException
|
14857 |
+
*/
|
14858 |
+
private function assertClient(ClientInterface $client)
|
14859 |
+
{
|
14860 |
+
if ($client->getConnection() instanceof AggregateConnectionInterface) {
|
14861 |
+
throw new NotSupportedException(
|
14862 |
+
'Cannot initialize a monitor consumer over aggregate connections.'
|
14863 |
+
);
|
14864 |
+
}
|
14865 |
+
|
14866 |
+
if ($client->getProfile()->supportsCommand('MONITOR') === false) {
|
14867 |
+
throw new NotSupportedException("The current profile does not support 'MONITOR'.");
|
14868 |
+
}
|
14869 |
+
}
|
14870 |
+
|
14871 |
+
/**
|
14872 |
+
* Initializes the consumer and sends the MONITOR command to the server.
|
14873 |
+
*/
|
14874 |
+
protected function start()
|
14875 |
+
{
|
14876 |
+
$this->client->executeCommand(
|
14877 |
+
$this->client->createCommand('MONITOR')
|
14878 |
+
);
|
14879 |
+
$this->valid = true;
|
14880 |
+
}
|
14881 |
+
|
14882 |
+
/**
|
14883 |
+
* Stops the consumer. Internally this is done by disconnecting from server
|
14884 |
+
* since there is no way to terminate the stream initialized by MONITOR.
|
14885 |
+
*/
|
14886 |
+
public function stop()
|
14887 |
+
{
|
14888 |
+
$this->client->disconnect();
|
14889 |
+
$this->valid = false;
|
14890 |
+
}
|
14891 |
+
|
14892 |
+
/**
|
14893 |
+
* {@inheritdoc}
|
14894 |
+
*/
|
14895 |
+
public function rewind()
|
14896 |
+
{
|
14897 |
+
// NOOP
|
14898 |
+
}
|
14899 |
+
|
14900 |
+
/**
|
14901 |
+
* Returns the last message payload retrieved from the server.
|
14902 |
+
*
|
14903 |
+
* @return Object
|
14904 |
+
*/
|
14905 |
+
public function current()
|
14906 |
+
{
|
14907 |
+
return $this->getValue();
|
14908 |
+
}
|
14909 |
+
|
14910 |
+
/**
|
14911 |
+
* {@inheritdoc}
|
14912 |
+
*/
|
14913 |
+
public function key()
|
14914 |
+
{
|
14915 |
+
return $this->position;
|
14916 |
+
}
|
14917 |
+
|
14918 |
+
/**
|
14919 |
+
* {@inheritdoc}
|
14920 |
+
*/
|
14921 |
+
public function next()
|
14922 |
+
{
|
14923 |
+
$this->position++;
|
14924 |
+
}
|
14925 |
+
|
14926 |
+
/**
|
14927 |
+
* Checks if the the consumer is still in a valid state to continue.
|
14928 |
+
*
|
14929 |
+
* @return bool
|
14930 |
+
*/
|
14931 |
+
public function valid()
|
14932 |
+
{
|
14933 |
+
return $this->valid;
|
14934 |
+
}
|
14935 |
+
|
14936 |
+
/**
|
14937 |
+
* Waits for a new message from the server generated by MONITOR and returns
|
14938 |
+
* it when available.
|
14939 |
+
*
|
14940 |
+
* @return Object
|
14941 |
+
*/
|
14942 |
+
private function getValue()
|
14943 |
+
{
|
14944 |
+
$database = 0;
|
14945 |
+
$client = null;
|
14946 |
+
$event = $this->client->getConnection()->read();
|
14947 |
+
|
14948 |
+
$callback = function ($matches) use (&$database, &$client) {
|
14949 |
+
if (2 === $count = count($matches)) {
|
14950 |
+
// Redis <= 2.4
|
14951 |
+
$database = (int) $matches[1];
|
14952 |
+
}
|
14953 |
+
|
14954 |
+
if (4 === $count) {
|
14955 |
+
// Redis >= 2.6
|
14956 |
+
$database = (int) $matches[2];
|
14957 |
+
$client = $matches[3];
|
14958 |
+
}
|
14959 |
+
|
14960 |
+
return ' ';
|
14961 |
+
};
|
14962 |
+
|
14963 |
+
$event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1);
|
14964 |
+
@list($timestamp, $command, $arguments) = explode(' ', $event, 3);
|
14965 |
+
|
14966 |
+
return (object) array(
|
14967 |
+
'timestamp' => (float) $timestamp,
|
14968 |
+
'database' => $database,
|
14969 |
+
'client' => $client,
|
14970 |
+
'command' => substr($command, 1, -1),
|
14971 |
+
'arguments' => $arguments,
|
14972 |
+
);
|
14973 |
+
}
|
14974 |
+
}
|
14975 |
+
|
14976 |
+
/* --------------------------------------------------------------------------- */
|
14977 |
+
|
14978 |
+
namespace Predis\Replication;
|
14979 |
+
|
14980 |
+
use Predis\NotSupportedException;
|
14981 |
+
use Predis\Command\CommandInterface;
|
14982 |
+
|
14983 |
+
/**
|
14984 |
+
* Defines a strategy for master/slave replication.
|
14985 |
+
*
|
14986 |
+
* @author Daniele Alessandri <suppakilla@gmail.com>
|
14987 |
+
*/
|
14988 |
+
class ReplicationStrategy
|
14989 |
+
{
|
14990 |
+
protected $disallowed;
|
14991 |
+
protected $readonly;
|
14992 |
+
protected $readonlySHA1;
|
14993 |
+
|
14994 |
+
/**
|
14995 |
+
*
|
14996 |
+
*/
|
14997 |
+
public function __construct()
|
14998 |
+
{
|
14999 |
+
$this->disallowed = $this->getDisallowedOperations();
|
15000 |
+
$this->readonly = $this->getReadOnlyOperations();
|
15001 |
+
$this->readonlySHA1 = array();
|
15002 |
+
}
|
15003 |
+
|
15004 |
+
/**
|
15005 |
+
* Returns if the specified command will perform a read-only operation
|
15006 |
+
* on Redis or not.
|
15007 |
+
*
|
15008 |
+
* @param CommandInterface $command Command instance.
|
15009 |
+
*
|
15010 |
+
* @return bool
|
15011 |
+
*
|
15012 |
+
* @throws NotSupportedException
|
15013 |
+
*/
|
15014 |
+
public function isReadOperation(CommandInterface $command)
|
15015 |
+
{
|
15016 |
+
if (isset($this->disallowed[$id = $command->getId()])) {
|
15017 |
+
throw new NotSupportedException(
|
15018 |
+
"The command '$id' is not allowed in replication mode."
|
15019 |
+
);
|
15020 |
+
}
|
15021 |
+
|
15022 |
+
if (isset($this->readonly[$id])) {
|
15023 |
+
if (true === $readonly = $this->readonly[$id]) {
|
15024 |
+
return true;
|
15025 |
+
}
|
15026 |
+
|
15027 |
+
return call_user_func($readonly, $command);
|
15028 |
+
}
|
15029 |
+
|
15030 |
+
if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
|
15031 |
+
$sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
|
15032 |
+
|
15033 |
+
if (isset($this->readonlySHA1[$sha1])) {
|
15034 |
+
if (true === $readonly = $this->readonlySHA1[$sha1]) {
|
15035 |
+
return true;
|
15036 |
+
}
|
15037 |
+
|
15038 |
+
return call_user_func($readonly, $command);
|
15039 |
+
}
|
15040 |
+
}
|
15041 |
+
|
15042 |
+
return false;
|
15043 |
+
}
|
15044 |
+
|
15045 |
+
/**
|
15046 |
+
* Returns if the specified command is not allowed for execution in a master
|
15047 |
+
* / slave replication context.
|
15048 |
+
*
|
15049 |
+
* @param CommandInterface $command Command instance.
|
15050 |
+
*
|
15051 |
+
* @return bool
|
15052 |
+
*/
|
15053 |
+
public function isDisallowedOperation(CommandInterface $command)
|
15054 |
+
{
|
15055 |
+
return isset($this->disallowed[$command->getId()]);
|
15056 |
+
}
|
15057 |
+
|
15058 |
+
/**
|
15059 |
+
* Checks if a SORT command is a readable operation by parsing the arguments
|
15060 |
+
* array of the specified commad instance.
|
15061 |
+
*
|
15062 |
+
* @param CommandInterface $command Command instance.
|
15063 |
+
*
|
15064 |
+
* @return bool
|
15065 |
+
*/
|
15066 |
+
protected function isSortReadOnly(CommandInterface $command)
|
15067 |
+
{
|
15068 |
+
$arguments = $command->getArguments();
|
15069 |
+
|
15070 |
+
return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
|
15071 |
+
}
|
15072 |
+
|
15073 |
+
/**
|
15074 |
+
* Marks a command as a read-only operation.
|
15075 |
+
*
|
15076 |
+
* When the behavior of a command can be decided only at runtime depending
|
15077 |
+
* on its arguments, a callable object can be provided to dynamically check
|
15078 |
+
* if the specified command performs a read or a write operation.
|
15079 |
+
*
|
15080 |
+
* @param string $commandID Command ID.
|
15081 |
+
* @param mixed $readonly A boolean value or a callable object.
|
15082 |
+
*/
|
15083 |
+
public function setCommandReadOnly($commandID, $readonly = true)
|
15084 |
+
{
|
15085 |
+
$commandID = strtoupper($commandID);
|
15086 |
+
|
15087 |
+
if ($readonly) {
|
15088 |
+
$this->readonly[$commandID] = $readonly;
|
15089 |
+
} else {
|
15090 |
+
unset($this->readonly[$commandID]);
|
15091 |
+
}
|
15092 |
+
}
|
15093 |
+
|
15094 |
+
/**
|
15095 |
+
* Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
|
15096 |
+
* the behaviour of a script can be decided only at runtime depending on
|
15097 |
+
* its arguments, a callable object can be provided to dynamically check
|
15098 |
+
* if the passed instance of EVAL or EVALSHA performs write operations or
|
15099 |
+
* not.
|
15100 |
+
*
|
15101 |
+
* @param string $script Body of the Lua script.
|
15102 |
+
* @param mixed $readonly A boolean value or a callable object.
|
15103 |
+
*/
|
15104 |
+
public function setScriptReadOnly($script, $readonly = true)
|
15105 |
+
{
|
15106 |
+
$sha1 = sha1($script);
|
15107 |
+
|
15108 |
+
if ($readonly) {
|
15109 |
+
$this->readonlySHA1[$sha1] = $readonly;
|
15110 |
+
} else {
|
15111 |
+
unset($this->readonlySHA1[$sha1]);
|
15112 |
+
}
|
15113 |
+
}
|
15114 |
+
|
15115 |
+
/**
|
15116 |
+
* Returns the default list of disallowed commands.
|
15117 |
+
*
|
15118 |
+
* @return array
|
15119 |
+
*/
|
15120 |
+
protected function getDisallowedOperations()
|
15121 |
+
{
|
15122 |
+
return array(
|
15123 |
+
'SHUTDOWN' => true,
|
15124 |
+
'INFO' => true,
|
15125 |
+
'DBSIZE' => true,
|
15126 |
+
'LASTSAVE' => true,
|
15127 |
+
'CONFIG' => true,
|
15128 |
+
'MONITOR' => true,
|
15129 |
+
'SLAVEOF' => true,
|
15130 |
+
'SAVE' => true,
|
15131 |
+
'BGSAVE' => true,
|
15132 |
+
'BGREWRITEAOF' => true,
|
15133 |
+
'SLOWLOG' => true,
|
15134 |
+
);
|
15135 |
+
}
|
15136 |
+
|
15137 |
+
/**
|
15138 |
+
* Returns the default list of commands performing read-only operations.
|
15139 |
+
*
|
15140 |
+
* @return array
|
15141 |
+
*/
|
15142 |
+
protected function getReadOnlyOperations()
|
15143 |
+
{
|
15144 |
+
return array(
|
15145 |
+
'EXISTS' => true,
|
15146 |
+
'TYPE' => true,
|
15147 |
+
'KEYS' => true,
|
15148 |
+
'SCAN' => true,
|
15149 |
+
'RANDOMKEY' => true,
|
15150 |
+
'TTL' => true,
|
15151 |
+
'GET' => true,
|
15152 |
+
'MGET' => true,
|
15153 |
+
'SUBSTR' => true,
|
15154 |
+
'STRLEN' => true,
|
15155 |
+
'GETRANGE' => true,
|
15156 |
+
'GETBIT' => true,
|
15157 |
+
'LLEN' => true,
|
15158 |
+
'LRANGE' => true,
|
15159 |
+
'LINDEX' => true,
|
15160 |
+
'SCARD' => true,
|
15161 |
+
'SISMEMBER' => true,
|
15162 |
+
'SINTER' => true,
|
15163 |
+
'SUNION' => true,
|
15164 |
+
'SDIFF' => true,
|
15165 |
+
'SMEMBERS' => true,
|
15166 |
+
'SSCAN' => true,
|
15167 |
+
'SRANDMEMBER' => true,
|
15168 |
+
'ZRANGE' => true,
|
15169 |
+
'ZREVRANGE' => true,
|
15170 |
+
'ZRANGEBYSCORE' => true,
|
15171 |
+
'ZREVRANGEBYSCORE' => true,
|
15172 |
+
'ZCARD' => true,
|
15173 |
+
'ZSCORE' => true,
|
15174 |
+
'ZCOUNT' => true,
|
15175 |
+
'ZRANK' => true,
|
15176 |
+
'ZREVRANK' => true,
|
15177 |
+
'ZSCAN' => true,
|
15178 |
+
'ZLEXCOUNT' => true,
|
15179 |
+
'ZRANGEBYLEX' => true,
|
15180 |
+
'HGET' => true,
|
15181 |
+
'HMGET' => true,
|
15182 |
+
'HEXISTS' => true,
|
15183 |
+
'HLEN' => true,
|
15184 |
+
'HKEYS' => true,
|
15185 |
+
'HVALS' => true,
|
15186 |
+
'HGETALL' => true,
|
15187 |
+
'HSCAN' => true,
|
15188 |
+
'PING' => true,
|
15189 |
+
'AUTH' => true,
|
15190 |
+
'SELECT' => true,
|
15191 |
+
'ECHO' => true,
|
15192 |
+
'QUIT' => true,
|
15193 |
+
'OBJECT' => true,
|
15194 |
+
'BITCOUNT' => true,
|
15195 |
+
'TIME' => true,
|
15196 |
+
'PFCOUNT' => true,
|
15197 |
+
'SORT' => array($this, 'isSortReadOnly'),
|
15198 |
+
);
|
15199 |
+
}
|
15200 |
+
}
|
15201 |
+
|
15202 |
+
/* --------------------------------------------------------------------------- */
|
15203 |
+
|
includes/redis-delete.php
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
//TODO:: phpRedis based implementation https://github.com/phpredis/phpredis#eval
|
4 |
+
//include predis (php implementation for redis)
|
5 |
+
require_once 'predis.php';
|
6 |
+
Predis\Autoloader::register();
|
7 |
+
|
8 |
+
global $myredis, $rt_wp_nginx_helper;
|
9 |
+
|
10 |
+
$host = $rt_wp_nginx_helper->options['redis_hostname'];
|
11 |
+
$port = $rt_wp_nginx_helper->options['redis_port'];
|
12 |
+
|
13 |
+
//Lua Script
|
14 |
+
$lua = <<<LUA
|
15 |
+
local k = 0
|
16 |
+
for i, name in ipairs(redis.call('KEYS', KEYS[1]))
|
17 |
+
do
|
18 |
+
redis.call('DEL', name)
|
19 |
+
k = k+1
|
20 |
+
end
|
21 |
+
return k
|
22 |
+
LUA;
|
23 |
+
|
24 |
+
//redis server parameter
|
25 |
+
$myredis = new Predis\Client( [
|
26 |
+
'host' => $host,
|
27 |
+
'port' => $port,
|
28 |
+
] );
|
29 |
+
|
30 |
+
//connect
|
31 |
+
try {
|
32 |
+
$myredis->connect();
|
33 |
+
} catch ( Exception $e ) {
|
34 |
+
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
/*
|
39 |
+
Delete multiple single keys without wildcard using redis pipeline feature to speed up things
|
40 |
+
*/
|
41 |
+
|
42 |
+
function delete_multi_keys( $key )
|
43 |
+
{
|
44 |
+
global $myredis;
|
45 |
+
$matching_keys = $myredis->keys( $key );
|
46 |
+
foreach ( $matching_keys as $key => $value ) {
|
47 |
+
$myredis->executeRaw( ['DEL', $value ] );
|
48 |
+
}
|
49 |
+
}
|
50 |
+
|
51 |
+
/*
|
52 |
+
* Delete all the keys from currently selected database
|
53 |
+
*/
|
54 |
+
|
55 |
+
function flush_entire_db()
|
56 |
+
{
|
57 |
+
global $myredis;
|
58 |
+
if ( !empty( $myredis ) ) {
|
59 |
+
return $myredis->flushdb();
|
60 |
+
} else {
|
61 |
+
return false;
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
/*
|
66 |
+
Single Key Delete Example
|
67 |
+
e.g. $key can be nginx-cache:httpsGETexample.com/
|
68 |
+
*/
|
69 |
+
|
70 |
+
function delete_single_key( $key )
|
71 |
+
{
|
72 |
+
global $myredis;
|
73 |
+
if ( !empty( $myredis ) ) {
|
74 |
+
return $myredis->executeRaw( ['DEL', $key ] );
|
75 |
+
} else {
|
76 |
+
return false;
|
77 |
+
}
|
78 |
+
}
|
79 |
+
|
80 |
+
/*
|
81 |
+
Delete Keys by wildcar
|
82 |
+
e.g. $key can be nginx-cache:httpsGETexample.com*
|
83 |
+
*/
|
84 |
+
|
85 |
+
function delete_keys_by_wildcard( $pattern )
|
86 |
+
{
|
87 |
+
global $myredis, $lua;
|
88 |
+
/*
|
89 |
+
Lua Script block to delete multiple keys using wildcard
|
90 |
+
Script will return count i.e. number of keys deleted
|
91 |
+
if return value is 0, that means no matches were found
|
92 |
+
*/
|
93 |
+
|
94 |
+
/*
|
95 |
+
Call redis eval and return value from lua script
|
96 |
+
*/
|
97 |
+
if ( !empty( $myredis ) ) {
|
98 |
+
return $myredis->eval( $lua, 1, $pattern );
|
99 |
+
} else {
|
100 |
+
return false;
|
101 |
+
}
|
102 |
+
}
|
103 |
+
|
104 |
+
?>
|
nginx-helper.php
CHANGED
@@ -3,398 +3,433 @@
|
|
3 |
Plugin Name: Nginx Helper
|
4 |
Plugin URI: https://rtcamp.com/nginx-helper/
|
5 |
Description: Cleans nginx's fastcgi/proxy cache whenever a post is edited/published. Also does few more things.
|
6 |
-
Version: 1.
|
7 |
Author: rtCamp
|
8 |
Author URI: https://rtcamp.com
|
9 |
Text Domain: nginx-helper
|
10 |
Requires at least: 3.0
|
11 |
-
Tested up to: 4.2
|
12 |
*/
|
13 |
|
14 |
namespace rtCamp\WP\Nginx {
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
}
|
305 |
|
306 |
namespace {
|
307 |
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
}
|
|
3 |
Plugin Name: Nginx Helper
|
4 |
Plugin URI: https://rtcamp.com/nginx-helper/
|
5 |
Description: Cleans nginx's fastcgi/proxy cache whenever a post is edited/published. Also does few more things.
|
6 |
+
Version: 1.9
|
7 |
Author: rtCamp
|
8 |
Author URI: https://rtcamp.com
|
9 |
Text Domain: nginx-helper
|
10 |
Requires at least: 3.0
|
11 |
+
Tested up to: 4.2.2
|
12 |
*/
|
13 |
|
14 |
namespace rtCamp\WP\Nginx {
|
15 |
+
define( 'rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH', plugin_dir_path( __FILE__ ) );
|
16 |
+
define( 'rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_URL', plugin_dir_url( __FILE__ ) );
|
17 |
+
|
18 |
+
class Helper {
|
19 |
+
|
20 |
+
var $minium_WP = '3.0';
|
21 |
+
var $options = null;
|
22 |
+
var $plugin_name = 'nginx-helper';
|
23 |
+
|
24 |
+
const WP_CLI_COMMAND = 'nginx-helper';
|
25 |
+
|
26 |
+
function __construct()
|
27 |
+
{
|
28 |
+
|
29 |
+
if ( !$this->required_wp_version() )
|
30 |
+
if ( !$this->required_php_version() )
|
31 |
+
return;
|
32 |
+
|
33 |
+
// Load Plugin Text Domain
|
34 |
+
add_action( 'init', array( $this, 'load_plugin_textdomain' ) );
|
35 |
+
|
36 |
+
$this->load_options();
|
37 |
+
$this->plugin_name = plugin_basename( __FILE__ );
|
38 |
+
|
39 |
+
register_activation_hook( $this->plugin_name, array( &$this, 'activate' ) );
|
40 |
+
register_deactivation_hook( $this->plugin_name, array( &$this, 'deactivate' ) );
|
41 |
+
|
42 |
+
add_action( 'init', array( &$this, 'start_helper' ), 15 );
|
43 |
+
}
|
44 |
+
|
45 |
+
function start_helper()
|
46 |
+
{
|
47 |
+
|
48 |
+
global $rt_wp_nginx_purger;
|
49 |
+
add_action( 'shutdown', array( &$this, 'add_timestamps' ), 99999 );
|
50 |
+
add_action( 'add_init', array( &$this, 'update_map' ) );
|
51 |
+
|
52 |
+
add_action( 'publish_post', array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
|
53 |
+
add_action( 'publish_page', array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
|
54 |
+
add_action( 'wp_insert_comment', array( &$rt_wp_nginx_purger, 'purgePostOnComment' ), 200, 2 );
|
55 |
+
add_action( 'transition_comment_status', array( &$rt_wp_nginx_purger, 'purgePostOnCommentChange' ), 200, 3 );
|
56 |
+
|
57 |
+
$args = array( '_builtin' => false );
|
58 |
+
$_rt_custom_post_types = get_post_types( $args );
|
59 |
+
if ( isset( $post_types ) && !empty( $post_types ) ) {
|
60 |
+
if ( $this->options['rt_wp_custom_post_types'] == true ) {
|
61 |
+
foreach ( $_rt_custom_post_types as $post_type ) {
|
62 |
+
add_action( 'publish_' . trim( $post_type ), array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
|
63 |
+
}
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
add_action( 'transition_post_status', array( &$this, 'set_future_post_option_on_future_status' ), 20, 3 );
|
68 |
+
add_action( 'delete_post', array( &$this, 'unset_future_post_option_on_delete' ), 20, 1 );
|
69 |
+
add_action( 'nm_check_log_file_size_daily', array( &$rt_wp_nginx_purger, 'checkAndTruncateLogFile' ), 100, 1 );
|
70 |
+
add_action( 'edit_attachment', array( &$rt_wp_nginx_purger, 'purgeImageOnEdit' ), 100, 1 );
|
71 |
+
add_action( 'wpmu_new_blog', array( &$this, 'update_new_blog_options' ), 10, 1 );
|
72 |
+
add_action( 'transition_post_status', array( &$rt_wp_nginx_purger, 'purge_on_post_moved_to_trash' ), 20, 3 );
|
73 |
+
add_action( 'edit_term', array( &$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited' ), 20, 3 );
|
74 |
+
add_action( 'delete_term', array( &$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited' ), 20, 3 );
|
75 |
+
add_action( 'check_ajax_referer', array( &$rt_wp_nginx_purger, 'purge_on_check_ajax_referer' ), 20, 2 );
|
76 |
+
add_action( 'admin_init', array( &$this, 'purge_all' ) );
|
77 |
+
|
78 |
+
// expose action to allow other plugins to purge the cache
|
79 |
+
add_action( 'rt_nginx_helper_purge_all', array( &$this, 'true_purge_all' ) );
|
80 |
+
|
81 |
+
// Load WP-CLI command
|
82 |
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
83 |
+
require_once RT_WP_NGINX_HELPER_PATH . 'wp-cli.php';
|
84 |
+
\WP_CLI::add_command( self::WP_CLI_COMMAND, 'Nginx_Helper_WP_CLI_Command' );
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
function activate()
|
89 |
+
{
|
90 |
+
|
91 |
+
$path = $this->functional_asset_path();
|
92 |
+
if ( !is_dir( $path ) ) {
|
93 |
+
mkdir( $path );
|
94 |
+
}
|
95 |
+
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
96 |
+
rt_wp_nginx_helper_install();
|
97 |
+
}
|
98 |
+
|
99 |
+
function deactivate()
|
100 |
+
{
|
101 |
+
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
102 |
+
rt_wp_nginx_helper_uninstall();
|
103 |
+
}
|
104 |
+
|
105 |
+
function required_wp_version()
|
106 |
+
{
|
107 |
+
|
108 |
+
global $wp_version;
|
109 |
+
$wp_ok = version_compare( $wp_version, $this->minium_WP, '>=' );
|
110 |
+
if ( ($wp_ok == FALSE ) ) {
|
111 |
+
add_action( 'admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
|
112 |
+
add_action( 'network_admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
|
113 |
+
return false;
|
114 |
+
}
|
115 |
+
|
116 |
+
return true;
|
117 |
+
}
|
118 |
+
|
119 |
+
function load_options()
|
120 |
+
{
|
121 |
+
$this->options = get_site_option( 'rt_wp_nginx_helper_options' );
|
122 |
+
}
|
123 |
+
|
124 |
+
function set_future_post_option_on_future_status( $new_status, $old_status, $post )
|
125 |
+
{
|
126 |
+
|
127 |
+
global $blog_id, $rt_wp_nginx_purger;
|
128 |
+
if ( !$this->options['enable_purge'] ) {
|
129 |
+
return;
|
130 |
+
}
|
131 |
+
if ( $old_status != $new_status && $old_status != 'inherit' && $new_status != 'inherit' && $old_status != 'auto-draft' && $new_status != 'auto-draft' && $new_status != 'publish' && !wp_is_post_revision( $post->ID ) ) {
|
132 |
+
$rt_wp_nginx_purger->log( "Purge post on transition post STATUS from " . $old_status . " to " . $new_status );
|
133 |
+
$rt_wp_nginx_purger->purgePost( $post->ID );
|
134 |
+
}
|
135 |
+
|
136 |
+
if ( $new_status == 'future' ) {
|
137 |
+
if ( $post && $post->post_status == 'future' && ( ( $post->post_type == 'post' || $post->post_type == 'page' ) || ( isset( $this->options['custom_post_types_recognized'] ) && in_array( $post->post_type, $this->options['custom_post_types_recognized'] ) ) ) ) {
|
138 |
+
$rt_wp_nginx_purger->log( "Set/update future_posts option (post id = " . $post->ID . " and blog id = " . $blog_id . ")" );
|
139 |
+
$this->options['future_posts'][$blog_id][$post->ID] = strtotime( $post->post_date_gmt ) + 60;
|
140 |
+
update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
|
141 |
+
}
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
function unset_future_post_option_on_delete( $post_id )
|
146 |
+
{
|
147 |
+
|
148 |
+
global $blog_id, $rt_wp_nginx_purger;
|
149 |
+
if ( !$this->options['enable_purge'] ) {
|
150 |
+
return;
|
151 |
+
}
|
152 |
+
if ( $post_id && !wp_is_post_revision( $post_id ) ) {
|
153 |
+
|
154 |
+
if ( isset( $this->options['future_posts'][$blog_id][$post_id] ) && count( $this->options['future_posts'][$blog_id][$post_id] ) ) {
|
155 |
+
$rt_wp_nginx_purger->log( "Unset future_posts option (post id = " . $post_id . " and blog id = " . $blog_id . ")" );
|
156 |
+
unset( $this->options['future_posts'][$blog_id][$post_id] );
|
157 |
+
update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
|
158 |
+
|
159 |
+
if ( !count( $this->options['future_posts'][$blog_id] ) ) {
|
160 |
+
unset( $this->options['future_posts'][$blog_id] );
|
161 |
+
update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
|
162 |
+
}
|
163 |
+
}
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
167 |
+
function update_new_blog_options( $blog_id )
|
168 |
+
{
|
169 |
+
global $rt_wp_nginx_purger;
|
170 |
+
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
171 |
+
$rt_wp_nginx_purger->log( "New site added (id $blog_id)" );
|
172 |
+
$this->update_map();
|
173 |
+
$rt_wp_nginx_purger->log( "New site added to nginx map (id $blog_id)" );
|
174 |
+
$helper_options = rt_wp_nginx_helper_get_options();
|
175 |
+
update_blog_option( $blog_id, "rt_wp_nginx_helper_options", $helper_options );
|
176 |
+
$rt_wp_nginx_purger->log( "Default options updated for the new blog (id $blog_id)" );
|
177 |
+
}
|
178 |
+
|
179 |
+
function get_map()
|
180 |
+
{
|
181 |
+
if ( !$this->options['enable_map'] ) {
|
182 |
+
return;
|
183 |
+
}
|
184 |
+
|
185 |
+
if ( is_multisite() ) {
|
186 |
+
|
187 |
+
global $wpdb;
|
188 |
+
|
189 |
+
$rt_all_blogs = $wpdb->get_results( $wpdb->prepare( "SELECT blog_id, domain, path FROM " . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0'", $wpdb->siteid ) );
|
190 |
+
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
191 |
+
$rt_domain_map_sites = '';
|
192 |
+
if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->dmtable}'" ) == $wpdb->dmtable ) {
|
193 |
+
$rt_domain_map_sites = $wpdb->get_results( "SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY id DESC" );
|
194 |
+
}
|
195 |
+
$rt_nginx_map = "";
|
196 |
+
$rt_nginx_map_array = array();
|
197 |
+
|
198 |
+
|
199 |
+
if ( $rt_all_blogs )
|
200 |
+
foreach ( $rt_all_blogs as $blog ) {
|
201 |
+
if ( SUBDOMAIN_INSTALL == "yes" ) {
|
202 |
+
$rt_nginx_map_array[$blog->domain] = $blog->blog_id;
|
203 |
+
} else {
|
204 |
+
if ( $blog->blog_id != 1 ) {
|
205 |
+
$rt_nginx_map_array[$blog->path] = $blog->blog_id;
|
206 |
+
}
|
207 |
+
}
|
208 |
+
}
|
209 |
+
|
210 |
+
if ( $rt_domain_map_sites ) {
|
211 |
+
foreach ( $rt_domain_map_sites as $site ) {
|
212 |
+
$rt_nginx_map_array[$site->domain] = $site->blog_id;
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
foreach ( $rt_nginx_map_array as $domain => $domain_id ) {
|
217 |
+
$rt_nginx_map .= "\t" . $domain . "\t" . $domain_id . ";\n";
|
218 |
+
}
|
219 |
+
|
220 |
+
return $rt_nginx_map;
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
function functional_asset_path()
|
225 |
+
{
|
226 |
+
$dir = wp_upload_dir();
|
227 |
+
$path = $dir['basedir'] . '/nginx-helper/';
|
228 |
+
return apply_filters( 'nginx_asset_path', $path );
|
229 |
+
}
|
230 |
+
|
231 |
+
function functional_asset_url()
|
232 |
+
{
|
233 |
+
$dir = wp_upload_dir();
|
234 |
+
$url = $dir['baseurl'] . '/nginx-helper/';
|
235 |
+
return apply_filters( 'nginx_asset_url', $url );
|
236 |
+
}
|
237 |
+
|
238 |
+
function update_map()
|
239 |
+
{
|
240 |
+
if ( is_multisite() ) {
|
241 |
+
$rt_nginx_map = $this->get_map();
|
242 |
+
|
243 |
+
if ( $fp = fopen( $this->functional_asset_path() . 'map.conf', 'w+' ) ) {
|
244 |
+
fwrite( $fp, $rt_nginx_map );
|
245 |
+
fclose( $fp );
|
246 |
+
return true;
|
247 |
+
}
|
248 |
+
}
|
249 |
+
}
|
250 |
+
|
251 |
+
function add_timestamps()
|
252 |
+
{
|
253 |
+
if ( $this->options['enable_purge'] != 1 )
|
254 |
+
return;
|
255 |
+
if ( $this->options['enable_stamp'] != 1 )
|
256 |
+
return;
|
257 |
+
if ( is_admin() )
|
258 |
+
return;
|
259 |
+
foreach ( headers_list() as $header ) {
|
260 |
+
list($key, $value) = explode( ':', $header, 2 );
|
261 |
+
if ( $key == 'Content-Type' && strpos( trim( $value ), 'text/html' ) !== 0 ) {
|
262 |
+
return;
|
263 |
+
}
|
264 |
+
if ( $key == 'Content-Type' )
|
265 |
+
break;
|
266 |
+
}
|
267 |
+
|
268 |
+
if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
|
269 |
+
return;
|
270 |
+
$timestamps = "\n<!--" .
|
271 |
+
"Cached using Nginx-Helper on " . current_time( 'mysql' ) . ". " .
|
272 |
+
"It took " . get_num_queries() . " queries executed in " . timer_stop() . " seconds." .
|
273 |
+
"-->\n" .
|
274 |
+
"<!--Visit http://wordpress.org/extend/plugins/nginx-helper/faq/ for more details-->";
|
275 |
+
echo $timestamps;
|
276 |
+
}
|
277 |
+
|
278 |
+
function show_notice()
|
279 |
+
{
|
280 |
+
echo '<div class="updated"><p>' . __( 'Purge initiated', 'nginx-helper' ) . '</p></div>';
|
281 |
+
}
|
282 |
+
|
283 |
+
function purge_all()
|
284 |
+
{
|
285 |
+
if ( !isset( $_REQUEST['nginx_helper_action'] ) )
|
286 |
+
return;
|
287 |
+
|
288 |
+
if ( !current_user_can( 'manage_options' ) )
|
289 |
+
wp_die( 'Sorry, you do not have the necessary privileges to edit these options.' );
|
290 |
+
|
291 |
+
$action = $_REQUEST['nginx_helper_action'];
|
292 |
+
|
293 |
+
if ( $action == 'done' ) {
|
294 |
+
add_action( 'admin_notices', array( &$this, 'show_notice' ) );
|
295 |
+
add_action( 'network_admin_notices', array( &$this, 'show_notice' ) );
|
296 |
+
return;
|
297 |
+
}
|
298 |
+
|
299 |
+
check_admin_referer( 'nginx_helper-purge_all' );
|
300 |
+
|
301 |
+
switch ( $action ) {
|
302 |
+
case 'purge':
|
303 |
+
$this->true_purge_all();
|
304 |
+
break;
|
305 |
+
}
|
306 |
+
wp_redirect( esc_url_raw( add_query_arg( array( 'nginx_helper_action' => 'done' ) ) ) );
|
307 |
+
}
|
308 |
+
|
309 |
+
function true_purge_all()
|
310 |
+
{
|
311 |
+
global $rt_wp_nginx_purger;
|
312 |
+
$rt_wp_nginx_purger->true_purge_all();
|
313 |
+
}
|
314 |
+
|
315 |
+
/**
|
316 |
+
* Load the translation file for current language.
|
317 |
+
*/
|
318 |
+
function load_plugin_textdomain()
|
319 |
+
{
|
320 |
+
load_plugin_textdomain( 'nginx-helper', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
|
321 |
+
}
|
322 |
+
|
323 |
+
}
|
324 |
+
|
325 |
}
|
326 |
|
327 |
namespace {
|
328 |
|
329 |
+
if ( !defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
|
330 |
+
define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/run/nginx-cache' );
|
331 |
+
}
|
332 |
+
global $current_blog;
|
333 |
+
|
334 |
+
if ( is_admin() ) {
|
335 |
+
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . '/admin/admin.php');
|
336 |
+
$rtwpAdminPanel = new \rtCamp\WP\Nginx\Admin();
|
337 |
+
}
|
338 |
+
|
339 |
+
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'purger.php');
|
340 |
+
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'redis-purger.php');
|
341 |
+
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'compatibility.php');
|
342 |
+
|
343 |
+
global $rt_wp_nginx_helper, $rt_wp_nginx_purger, $rt_wp_nginx_compatibility;
|
344 |
+
$rt_wp_nginx_helper = new \rtCamp\WP\Nginx\Helper;
|
345 |
+
|
346 |
+
if ( !empty( $rt_wp_nginx_helper->options['cache_method'] ) && $rt_wp_nginx_helper->options['cache_method'] == "enable_redis" ) {
|
347 |
+
$rt_wp_nginx_purger = new \rtCamp\WP\Nginx\Redispurger;
|
348 |
+
} else {
|
349 |
+
$rt_wp_nginx_purger = new \rtCamp\WP\Nginx\Purger;
|
350 |
+
}
|
351 |
+
$rt_wp_nginx_compatibility = namespace\rtCamp\WP\Nginx\Compatibility::instance();
|
352 |
+
if ( $rt_wp_nginx_compatibility->haveNginx() && !function_exists( 'wp_redirect' ) ) {
|
353 |
+
|
354 |
+
function wp_redirect( $location, $status = 302 )
|
355 |
+
{
|
356 |
+
$location = apply_filters( 'wp_redirect', $location, $status );
|
357 |
+
|
358 |
+
if ( empty( $location ) ) {
|
359 |
+
return false;
|
360 |
+
}
|
361 |
+
|
362 |
+
$status = apply_filters( 'wp_redirect_status', $status, $location );
|
363 |
+
if ( $status < 300 || $status > 399 ) {
|
364 |
+
$status = 302;
|
365 |
+
}
|
366 |
+
|
367 |
+
if ( function_exists( 'wp_sanitize_redirect' ) ) {
|
368 |
+
$location = wp_sanitize_redirect( $location );
|
369 |
+
}
|
370 |
+
header( 'Location: ' . $location, true, $status );
|
371 |
+
}
|
372 |
+
|
373 |
+
}
|
374 |
+
|
375 |
+
// Add settings link on plugin page
|
376 |
+
function nginx_settings_link( $links )
|
377 |
+
{
|
378 |
+
if ( is_network_admin() ) {
|
379 |
+
$u = 'settings.php';
|
380 |
+
} else {
|
381 |
+
$u = 'options-general.php';
|
382 |
+
}
|
383 |
+
$settings_link = '<a href="' . $u . '?page=nginx">' . __( 'Settings', 'nginx-helper' ) . '</a>';
|
384 |
+
array_unshift( $links, $settings_link );
|
385 |
+
return $links;
|
386 |
+
}
|
387 |
+
|
388 |
+
if ( is_multisite() ) {
|
389 |
+
add_filter( "network_admin_plugin_action_links_" . plugin_basename( __FILE__ ), 'nginx_settings_link' );
|
390 |
+
} else {
|
391 |
+
add_filter( "plugin_action_links_" . plugin_basename( __FILE__ ), 'nginx_settings_link' );
|
392 |
+
}
|
393 |
+
|
394 |
+
function get_feeds( $feed_url = 'http://rtcamp.com/blog/feed/' )
|
395 |
+
{
|
396 |
+
// Get RSS Feed(s)
|
397 |
+
require_once( ABSPATH . WPINC . '/feed.php' );
|
398 |
+
$maxitems = 0;
|
399 |
+
// Get a SimplePie feed object from the specified feed source.
|
400 |
+
$rss = fetch_feed( $feed_url );
|
401 |
+
if ( !is_wp_error( $rss ) ) { // Checks that the object is created correctly
|
402 |
+
// Figure out how many total items there are, but limit it to 5.
|
403 |
+
$maxitems = $rss->get_item_quantity( 5 );
|
404 |
+
|
405 |
+
// Build an array of all the items, starting with element 0 (first element).
|
406 |
+
$rss_items = $rss->get_items( 0, $maxitems );
|
407 |
+
}
|
408 |
+
?>
|
409 |
+
<ul role="list"><?php
|
410 |
+
if ( $maxitems == 0 ) {
|
411 |
+
echo '<li role="listitem">' . __( 'No items', 'nginx-helper' ) . '.</li>';
|
412 |
+
} else {
|
413 |
+
// Loop through each feed item and display each item as a hyperlink.
|
414 |
+
foreach ( $rss_items as $item ) {
|
415 |
+
?>
|
416 |
+
<li role="listitem">
|
417 |
+
<a href='<?php echo $item->get_permalink(); ?>' title='<?php echo __( 'Posted ', 'nginx-helper' ) . $item->get_date( 'j F Y | g:i a' ); ?>'><?php echo $item->get_title(); ?></a>
|
418 |
+
</li><?php
|
419 |
+
}
|
420 |
+
}
|
421 |
+
?>
|
422 |
+
</ul><?php
|
423 |
+
}
|
424 |
+
|
425 |
+
function fetch_feeds()
|
426 |
+
{
|
427 |
+
if ( isset( $_GET['get_feeds'] ) && $_GET['get_feeds'] == '1' ) {
|
428 |
+
get_feeds();
|
429 |
+
die();
|
430 |
+
}
|
431 |
+
}
|
432 |
+
|
433 |
+
add_action( 'init', 'fetch_feeds' );
|
434 |
}
|
435 |
+
|
readme.txt
CHANGED
@@ -1,9 +1,9 @@
|
|
1 |
=== Nginx Helper ===
|
2 |
-
Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, Darren Slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx
|
3 |
Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, rewrite, permalinks
|
4 |
Requires at least: 3.0
|
5 |
-
Tested up to: 4.2
|
6 |
-
Stable tag: 1.
|
7 |
License: GPLv2 or later (of-course)
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
Donate Link: http://rtcamp.com/donate/
|
@@ -113,6 +113,9 @@ Please post your problem in [our free support forum](http://community.rtcamp.com
|
|
113 |
|
114 |
== Changelog ==
|
115 |
|
|
|
|
|
|
|
116 |
= 1.8.13 =
|
117 |
Fixed PHP notice for an undefined index when "Enable Logging" is not set.
|
118 |
|
@@ -293,5 +296,5 @@ Fix url escaping [#82](https://github.com/rtCamp/nginx-helper/pull/82) - by
|
|
293 |
|
294 |
== Upgrade Notice ==
|
295 |
|
296 |
-
= 1.
|
297 |
-
|
1 |
=== Nginx Helper ===
|
2 |
+
Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, Darren Slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx, rohanveer
|
3 |
Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, rewrite, permalinks
|
4 |
Requires at least: 3.0
|
5 |
+
Tested up to: 4.2.2
|
6 |
+
Stable tag: 1.9
|
7 |
License: GPLv2 or later (of-course)
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
Donate Link: http://rtcamp.com/donate/
|
113 |
|
114 |
== Changelog ==
|
115 |
|
116 |
+
= 1.9 =
|
117 |
+
Added Redis cache purge support.
|
118 |
+
|
119 |
= 1.8.13 =
|
120 |
Fixed PHP notice for an undefined index when "Enable Logging" is not set.
|
121 |
|
296 |
|
297 |
== Upgrade Notice ==
|
298 |
|
299 |
+
= 1.9 =
|
300 |
+
Added Redis cache purge support.
|
trunk/purger.php → redis-purger.php
RENAMED
@@ -2,9 +2,15 @@
|
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
4 |
|
5 |
-
class
|
6 |
|
7 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
$oldstatus = '';
|
9 |
$approved = $comment->comment_approved;
|
10 |
|
@@ -24,10 +30,11 @@ namespace rtCamp\WP\Nginx {
|
|
24 |
$this->purgePostOnCommentChange( $newstatus, $oldstatus, $comment );
|
25 |
}
|
26 |
|
27 |
-
function purgePostOnCommentChange( $newstatus, $oldstatus, $comment )
|
|
|
28 |
|
29 |
global $rt_wp_nginx_helper, $blog_id;
|
30 |
-
if (
|
31 |
return;
|
32 |
}
|
33 |
|
@@ -42,7 +49,7 @@ namespace rtCamp\WP\Nginx {
|
|
42 |
$this->log( "* Status Changed from $oldstatus to $newstatus" );
|
43 |
switch ( $newstatus ) {
|
44 |
case 'approved':
|
45 |
-
if ( $rt_wp_nginx_helper->options[
|
46 |
$this->log( "* Comment ($_comment_id) approved. Post ($_post_id) purging..." );
|
47 |
$this->log( "* * * * *" );
|
48 |
$this->purgePost( $_post_id );
|
@@ -52,7 +59,7 @@ namespace rtCamp\WP\Nginx {
|
|
52 |
case 'unapproved':
|
53 |
case 'trash':
|
54 |
if ( $oldstatus == 'approve' ) {
|
55 |
-
if ( $rt_wp_nginx_helper->options[
|
56 |
$this->log( "* Comment ($_comment_id) removed as ($newstatus). Post ($_post_id) purging..." );
|
57 |
$this->log( "* * * * *" );
|
58 |
$this->purgePost( $_post_id );
|
@@ -62,10 +69,10 @@ namespace rtCamp\WP\Nginx {
|
|
62 |
}
|
63 |
}
|
64 |
|
65 |
-
function purgePost( $_ID )
|
66 |
-
|
67 |
global $rt_wp_nginx_helper, $blog_id;
|
68 |
-
if (
|
69 |
return;
|
70 |
}
|
71 |
switch ( current_filter() ) {
|
@@ -101,7 +108,7 @@ namespace rtCamp\WP\Nginx {
|
|
101 |
|
102 |
$this->log( "Function purgePost BEGIN ===" );
|
103 |
|
104 |
-
if ( $rt_wp_nginx_helper->options[
|
105 |
$homepage_url = trailingslashit( home_url() );
|
106 |
|
107 |
$this->log( "Purging homepage '$homepage_url'" );
|
@@ -110,16 +117,16 @@ namespace rtCamp\WP\Nginx {
|
|
110 |
|
111 |
|
112 |
if ( current_filter() == 'comment_post' || current_filter() == 'wp_set_comment_status' ) {
|
113 |
-
$this->_purge_by_options( $_ID, $blog_id, $rt_wp_nginx_helper->options[
|
114 |
} else {
|
115 |
-
$this->_purge_by_options( $_ID, $blog_id, $rt_wp_nginx_helper->options[
|
116 |
}
|
117 |
|
118 |
$this->log( "Function purgePost END ^^^" );
|
119 |
}
|
120 |
|
121 |
-
private function _purge_by_options( $_post_ID, $blog_id, $_purge_page, $_purge_archive, $_purge_custom_taxa )
|
122 |
-
|
123 |
global $rt_wp_nginx_helper;
|
124 |
|
125 |
$_post_type = get_post_type( $_post_ID );
|
@@ -187,7 +194,7 @@ namespace rtCamp\WP\Nginx {
|
|
187 |
$this->log( "Purging custom taxonomies related" );
|
188 |
foreach ( $custom_taxonomies as $taxon ) {
|
189 |
|
190 |
-
if ( !
|
191 |
|
192 |
if ( $terms = get_the_terms( $_post_ID, $taxon ) ) {
|
193 |
foreach ( $terms as $term ) {
|
@@ -202,147 +209,30 @@ namespace rtCamp\WP\Nginx {
|
|
202 |
}
|
203 |
}
|
204 |
|
205 |
-
function purgeUrl( $url, $feed = true )
|
206 |
-
|
207 |
global $rt_wp_nginx_helper;
|
208 |
|
209 |
$this->log( "- Purging URL | " . $url );
|
210 |
|
211 |
$parse = parse_url( $url );
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
$_url_purge = $_url_purge_base;
|
217 |
-
|
218 |
-
if ( isset( $parse[ 'query' ] ) && $parse[ 'query' ] != '' ) {
|
219 |
-
$_url_purge .= '?' . $parse[ 'query' ];
|
220 |
-
}
|
221 |
-
|
222 |
-
$this->_delete_cache_file_for( $_url_purge );
|
223 |
-
|
224 |
-
if ( $feed ) {
|
225 |
-
$feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
|
226 |
-
$this->_delete_cache_file_for( $feed_url );
|
227 |
-
$this->_delete_cache_file_for( $feed_url . 'atom/' );
|
228 |
-
$this->_delete_cache_file_for( $feed_url . 'rdf/' );
|
229 |
-
}
|
230 |
-
break;
|
231 |
-
case 'get_request':
|
232 |
-
// Go to default case
|
233 |
-
default:
|
234 |
-
$_url_purge_base = $parse[ 'scheme' ] . '://' . $parse[ 'host' ] . '/purge' . $parse[ 'path' ];
|
235 |
-
$_url_purge = $_url_purge_base;
|
236 |
-
|
237 |
-
if ( isset( $parse[ 'query' ] ) && $parse[ 'query' ] != '' ) {
|
238 |
-
$_url_purge .= '?' . $parse[ 'query' ];
|
239 |
-
}
|
240 |
-
|
241 |
-
$this->_do_remote_get( $_url_purge );
|
242 |
-
|
243 |
-
if ( $feed ) {
|
244 |
-
$feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
|
245 |
-
$this->_do_remote_get( $feed_url );
|
246 |
-
$this->_do_remote_get( $feed_url . 'atom/' );
|
247 |
-
$this->_do_remote_get( $feed_url . 'rdf/' );
|
248 |
-
}
|
249 |
-
break;
|
250 |
-
}
|
251 |
-
|
252 |
}
|
253 |
|
254 |
-
|
255 |
-
|
256 |
-
*
|
257 |
-
* Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri").
|
258 |
-
*
|
259 |
-
* Read more on how this works here:
|
260 |
-
* https://www.digitalocean.com/community/tutorials/how-to-setup-fastcgi-caching-with-nginx-on-your-vps#purging-the-cache
|
261 |
-
**********************/
|
262 |
-
private function _delete_cache_file_for( $url ) {
|
263 |
-
|
264 |
-
// Verify cache path is set
|
265 |
-
if (!defined('RT_WP_NGINX_HELPER_CACHE_PATH')) {
|
266 |
-
$this->log('Error purging because RT_WP_NGINX_HELPER_CACHE_PATH was not defined. URL: '.$url, 'ERROR');
|
267 |
-
return false;
|
268 |
-
}
|
269 |
-
|
270 |
-
// Verify URL is valid
|
271 |
-
$url_data = parse_url($url);
|
272 |
-
if(!$url_data) {
|
273 |
-
$this->log('Error purging because specified URL did not appear to be valid. URL: '.$url, 'ERROR');
|
274 |
-
return false;
|
275 |
-
}
|
276 |
-
|
277 |
-
// Build a hash of the URL
|
278 |
-
$hash = md5($url_data['scheme'].'GET'.$url_data['host'].$url_data['path']);
|
279 |
-
|
280 |
-
// Ensure trailing slash
|
281 |
-
$cache_path = RT_WP_NGINX_HELPER_CACHE_PATH;
|
282 |
-
$cache_path = (substr($cache_path, -1) == '/') ? $cache_path : $cache_path.'/';
|
283 |
-
|
284 |
-
// Set path to cached file
|
285 |
-
$cached_file = $cache_path . substr($hash, -1) . '/' . substr($hash,-3,2) . '/' . $hash;
|
286 |
-
|
287 |
-
// Verify cached file exists
|
288 |
-
if (!file_exists($cached_file)) {
|
289 |
-
$this->log( "- - " . $url . " is currently not cached (checked for file: $cached_file)" );
|
290 |
-
return false;
|
291 |
-
}
|
292 |
-
|
293 |
-
// Delete the cached file
|
294 |
-
if (unlink($cached_file)) {
|
295 |
-
$this->log( "- - " . $url . " *** PURGED ***" );
|
296 |
-
} else {
|
297 |
-
$this->log("- - An error occurred deleting the cache file. Check the server logs for a PHP warning.", "ERROR");
|
298 |
-
}
|
299 |
-
}
|
300 |
-
|
301 |
-
private function _do_remote_get( $url ) {
|
302 |
-
|
303 |
-
$response = wp_remote_get( $url );
|
304 |
-
|
305 |
-
if ( is_wp_error( $response ) ) {
|
306 |
-
$_errors_str = implode( " - ", $response->get_error_messages() );
|
307 |
-
$this->log( "Error while purging URL. " . $_errors_str, "ERROR" );
|
308 |
-
} else {
|
309 |
-
if ( $response[ 'response' ][ 'code' ] ) {
|
310 |
-
switch ( $response[ 'response' ][ 'code' ] ) {
|
311 |
-
case 200:
|
312 |
-
$this->log( "- - " . $url . " *** PURGED ***" );
|
313 |
-
break;
|
314 |
-
case 404:
|
315 |
-
$this->log( "- - " . $url . " is currently not cached" );
|
316 |
-
break;
|
317 |
-
default:
|
318 |
-
$this->log( "- - " . $url . " not found (" . $response[ 'response' ][ 'code' ] . ")", "WARNING" );
|
319 |
-
}
|
320 |
-
}
|
321 |
-
}
|
322 |
-
}
|
323 |
-
|
324 |
-
function checkHttpConnection() {
|
325 |
-
|
326 |
-
$purgeURL = plugins_url( "nginx-manager/check-proxy.php" );
|
327 |
-
$response = wp_remote_get( $purgeURL );
|
328 |
-
|
329 |
-
if ( ! is_wp_error( $response ) && ($response[ 'body' ] == 'HTTP Connection OK') ) {
|
330 |
-
return "OK";
|
331 |
-
}
|
332 |
-
|
333 |
-
return "KO";
|
334 |
-
}
|
335 |
-
|
336 |
-
function log( $msg, $level = 'INFO' ) {
|
337 |
|
338 |
global $rt_wp_nginx_helper;
|
339 |
-
if (
|
340 |
return;
|
341 |
}
|
342 |
|
343 |
$log_levels = array( "INFO" => 0, "WARNING" => 1, "ERROR" => 2, "NONE" => 3 );
|
344 |
|
345 |
-
if ( $log_levels[
|
346 |
if ( $fp = fopen( $rt_wp_nginx_helper->functional_asset_path() . 'nginx.log', "a+" ) ) {
|
347 |
fwrite( $fp, "\n" . gmdate( "Y-m-d H:i:s " ) . " | " . $level . " | " . $msg );
|
348 |
fclose( $fp );
|
@@ -352,11 +242,12 @@ namespace rtCamp\WP\Nginx {
|
|
352 |
return true;
|
353 |
}
|
354 |
|
355 |
-
function checkAndTruncateLogFile()
|
|
|
356 |
|
357 |
global $rt_wp_nginx_helper;
|
358 |
|
359 |
-
$maxSizeAllowed = (is_numeric( $rt_wp_nginx_helper->options[
|
360 |
|
361 |
$fileSize = filesize( $rt_wp_nginx_helper->functional_asset_path() . 'nginx.log' );
|
362 |
|
@@ -377,7 +268,8 @@ namespace rtCamp\WP\Nginx {
|
|
377 |
}
|
378 |
}
|
379 |
|
380 |
-
function purgeImageOnEdit( $attachment_id )
|
|
|
381 |
|
382 |
$this->log( "Purging media on edit BEGIN ===" );
|
383 |
|
@@ -386,11 +278,11 @@ namespace rtCamp\WP\Nginx {
|
|
386 |
$this->purgeUrl( wp_get_attachment_url( $attachment_id ), false );
|
387 |
$attachment = wp_get_attachment_metadata( $attachment_id );
|
388 |
|
389 |
-
if ( !
|
390 |
-
foreach ( $attachment[
|
391 |
$resize_image = wp_get_attachment_image_src( $attachment_id, $size_name );
|
392 |
if ( $resize_image )
|
393 |
-
$this->purgeUrl( $resize_image[
|
394 |
}
|
395 |
}
|
396 |
$this->purgeURL( get_attachment_link( $attachment_id ) );
|
@@ -401,10 +293,11 @@ namespace rtCamp\WP\Nginx {
|
|
401 |
$this->log( "Purging media on edit END ^^^" );
|
402 |
}
|
403 |
|
404 |
-
function purge_on_post_moved_to_trash( $new_status, $old_status, $post )
|
|
|
405 |
|
406 |
global $rt_wp_nginx_helper, $blog_id;
|
407 |
-
if (
|
408 |
return;
|
409 |
}
|
410 |
if ( $new_status == 'trash' ) {
|
@@ -417,12 +310,12 @@ namespace rtCamp\WP\Nginx {
|
|
417 |
|
418 |
|
419 |
|
420 |
-
if ( $rt_wp_nginx_helper->options[
|
421 |
$this->_purge_homepage();
|
422 |
}
|
423 |
|
424 |
|
425 |
-
$this->_purge_by_options( $post->ID, $blog_id, false, $rt_wp_nginx_helper->options[
|
426 |
|
427 |
|
428 |
|
@@ -432,7 +325,8 @@ namespace rtCamp\WP\Nginx {
|
|
432 |
return true;
|
433 |
}
|
434 |
|
435 |
-
private function _purge_homepage()
|
|
|
436 |
|
437 |
$homepage_url = trailingslashit( home_url() );
|
438 |
|
@@ -442,15 +336,16 @@ namespace rtCamp\WP\Nginx {
|
|
442 |
return true;
|
443 |
}
|
444 |
|
445 |
-
private function _purge_personal_urls()
|
|
|
446 |
|
447 |
global $rt_wp_nginx_helper;
|
448 |
|
449 |
$this->log( __( "Purging personal urls", "nginx-helper" ) );
|
450 |
|
451 |
-
if ( isset( $rt_wp_nginx_helper->options[
|
452 |
|
453 |
-
foreach ( $rt_wp_nginx_helper->options[
|
454 |
$this->purgeUrl( $u, false );
|
455 |
}
|
456 |
} else {
|
@@ -460,7 +355,8 @@ namespace rtCamp\WP\Nginx {
|
|
460 |
return true;
|
461 |
}
|
462 |
|
463 |
-
private function _purge_post_categories( $_post_id )
|
|
|
464 |
|
465 |
$this->log( __( "Purging category archives", "nginx-helper" ) );
|
466 |
|
@@ -474,7 +370,8 @@ namespace rtCamp\WP\Nginx {
|
|
474 |
return true;
|
475 |
}
|
476 |
|
477 |
-
private function _purge_post_tags( $_post_id )
|
|
|
478 |
|
479 |
$this->log( __( "Purging tags archives", "nginx-helper" ) );
|
480 |
|
@@ -488,7 +385,8 @@ namespace rtCamp\WP\Nginx {
|
|
488 |
return true;
|
489 |
}
|
490 |
|
491 |
-
private function _purge_post_custom_taxa( $_post_id )
|
|
|
492 |
|
493 |
$this->log( __( "Purging post custom taxonomies related", "nginx-helper" ) );
|
494 |
|
@@ -496,7 +394,7 @@ namespace rtCamp\WP\Nginx {
|
|
496 |
foreach ( $custom_taxonomies as $taxon ) {
|
497 |
$this->log( sprintf( "+ " . __( "Purging custom taxonomy '%s'", "nginx-helper" ), $taxon ) );
|
498 |
|
499 |
-
if ( !
|
500 |
|
501 |
if ( $terms = get_the_terms( $_post_id, $taxon ) ) {
|
502 |
foreach ( $terms as $term ) {
|
@@ -514,7 +412,8 @@ namespace rtCamp\WP\Nginx {
|
|
514 |
return true;
|
515 |
}
|
516 |
|
517 |
-
private function _purge_all_categories()
|
|
|
518 |
|
519 |
$this->log( __( "Purging all categories", "nginx-helper" ) );
|
520 |
|
@@ -531,7 +430,8 @@ namespace rtCamp\WP\Nginx {
|
|
531 |
return true;
|
532 |
}
|
533 |
|
534 |
-
private function _purge_all_posttags()
|
|
|
535 |
|
536 |
$this->log( __( "Purging all tags", "nginx-helper" ) );
|
537 |
|
@@ -548,7 +448,8 @@ namespace rtCamp\WP\Nginx {
|
|
548 |
return true;
|
549 |
}
|
550 |
|
551 |
-
private function _purge_all_customtaxa()
|
|
|
552 |
|
553 |
$this->log( __( "Purging all custom taxonomies", "nginx-helper" ) );
|
554 |
|
@@ -557,7 +458,7 @@ namespace rtCamp\WP\Nginx {
|
|
557 |
foreach ( $custom_taxonomies as $taxon ) {
|
558 |
$this->log( sprintf( "+ " . __( "Purging custom taxonomy '%s'", "nginx-helper" ), $taxon ) );
|
559 |
|
560 |
-
if ( !
|
561 |
|
562 |
if ( $terms = get_terms( $taxon ) ) {
|
563 |
foreach ( $terms as $term ) {
|
@@ -575,7 +476,8 @@ namespace rtCamp\WP\Nginx {
|
|
575 |
return true;
|
576 |
}
|
577 |
|
578 |
-
private function _purge_all_taxonomies()
|
|
|
579 |
|
580 |
$this->_purge_all_categories();
|
581 |
$this->_purge_all_posttags();
|
@@ -584,7 +486,8 @@ namespace rtCamp\WP\Nginx {
|
|
584 |
return true;
|
585 |
}
|
586 |
|
587 |
-
private function _purge_all_posts()
|
|
|
588 |
|
589 |
$this->log( __( "Purging all posts, pages and custom post types.", "nginx-helper" ) );
|
590 |
|
@@ -606,7 +509,8 @@ namespace rtCamp\WP\Nginx {
|
|
606 |
return true;
|
607 |
}
|
608 |
|
609 |
-
private function _purge_all_date_archives()
|
|
|
610 |
|
611 |
$this->log( __( "Purging all date-based archives.", "nginx-helper" ) );
|
612 |
|
@@ -619,7 +523,8 @@ namespace rtCamp\WP\Nginx {
|
|
619 |
return true;
|
620 |
}
|
621 |
|
622 |
-
private function _purge_all_daily_archives()
|
|
|
623 |
|
624 |
global $wpdb;
|
625 |
|
@@ -644,7 +549,8 @@ namespace rtCamp\WP\Nginx {
|
|
644 |
}
|
645 |
}
|
646 |
|
647 |
-
private function _purge_all_monthly_archives()
|
|
|
648 |
|
649 |
global $wpdb;
|
650 |
|
@@ -669,7 +575,8 @@ namespace rtCamp\WP\Nginx {
|
|
669 |
}
|
670 |
}
|
671 |
|
672 |
-
private function _purge_all_yearly_archives()
|
|
|
673 |
|
674 |
global $wpdb;
|
675 |
|
@@ -694,7 +601,8 @@ namespace rtCamp\WP\Nginx {
|
|
694 |
}
|
695 |
}
|
696 |
|
697 |
-
function purge_them_all()
|
|
|
698 |
|
699 |
$this->log( __( "Let's purge everything!", "nginx-helper" ) );
|
700 |
|
@@ -713,7 +621,8 @@ namespace rtCamp\WP\Nginx {
|
|
713 |
return true;
|
714 |
}
|
715 |
|
716 |
-
function purge_on_term_taxonomy_edited( $term_id, $tt_id, $taxon )
|
|
|
717 |
|
718 |
$this->log( __( "Term taxonomy edited or deleted", "nginx-helper" ) );
|
719 |
|
@@ -728,7 +637,8 @@ namespace rtCamp\WP\Nginx {
|
|
728 |
return true;
|
729 |
}
|
730 |
|
731 |
-
function purge_on_check_ajax_referer( $action, $result )
|
|
|
732 |
|
733 |
switch ( $action ) {
|
734 |
case 'save-sidebar-widgets' :
|
@@ -746,36 +656,15 @@ namespace rtCamp\WP\Nginx {
|
|
746 |
return true;
|
747 |
}
|
748 |
|
749 |
-
function true_purge_all()
|
750 |
-
|
|
|
751 |
$this->log( "* * * * *" );
|
752 |
$this->log( "* Purged Everything!" );
|
753 |
$this->log( "* * * * *" );
|
|
|
754 |
}
|
755 |
|
756 |
-
/** Source - http://stackoverflow.com/a/1360437/156336 **/
|
757 |
-
|
758 |
-
function unlinkRecursive( $dir, $deleteRootToo ) {
|
759 |
-
if ( ! $dh = opendir( $dir ) ) {
|
760 |
-
return;
|
761 |
-
}
|
762 |
-
while ( false !== ($obj = readdir( $dh )) ) {
|
763 |
-
if ( $obj == '.' || $obj == '..' ) {
|
764 |
-
continue;
|
765 |
-
}
|
766 |
-
|
767 |
-
if ( ! @unlink( $dir . '/' . $obj ) ) {
|
768 |
-
$this->unlinkRecursive( $dir . '/' . $obj, true );
|
769 |
-
}
|
770 |
-
}
|
771 |
-
|
772 |
-
if ($deleteRootToo){
|
773 |
-
rmdir($dir);
|
774 |
-
}
|
775 |
-
|
776 |
-
closedir( $dh );
|
777 |
-
|
778 |
-
return;
|
779 |
-
}
|
780 |
}
|
|
|
781 |
}
|
2 |
|
3 |
namespace rtCamp\WP\Nginx {
|
4 |
|
5 |
+
class Redispurger {
|
6 |
|
7 |
+
public function __construct()
|
8 |
+
{
|
9 |
+
require_once RT_WP_NGINX_HELPER_PATH . 'includes/redis-delete.php';
|
10 |
+
}
|
11 |
+
|
12 |
+
function purgePostOnComment( $comment_id, $comment )
|
13 |
+
{
|
14 |
$oldstatus = '';
|
15 |
$approved = $comment->comment_approved;
|
16 |
|
30 |
$this->purgePostOnCommentChange( $newstatus, $oldstatus, $comment );
|
31 |
}
|
32 |
|
33 |
+
function purgePostOnCommentChange( $newstatus, $oldstatus, $comment )
|
34 |
+
{
|
35 |
|
36 |
global $rt_wp_nginx_helper, $blog_id;
|
37 |
+
if ( !$rt_wp_nginx_helper->options['enable_purge'] ) {
|
38 |
return;
|
39 |
}
|
40 |
|
49 |
$this->log( "* Status Changed from $oldstatus to $newstatus" );
|
50 |
switch ( $newstatus ) {
|
51 |
case 'approved':
|
52 |
+
if ( $rt_wp_nginx_helper->options['purge_page_on_new_comment'] == 1 ) {
|
53 |
$this->log( "* Comment ($_comment_id) approved. Post ($_post_id) purging..." );
|
54 |
$this->log( "* * * * *" );
|
55 |
$this->purgePost( $_post_id );
|
59 |
case 'unapproved':
|
60 |
case 'trash':
|
61 |
if ( $oldstatus == 'approve' ) {
|
62 |
+
if ( $rt_wp_nginx_helper->options['purge_page_on_deleted_comment'] == 1 ) {
|
63 |
$this->log( "* Comment ($_comment_id) removed as ($newstatus). Post ($_post_id) purging..." );
|
64 |
$this->log( "* * * * *" );
|
65 |
$this->purgePost( $_post_id );
|
69 |
}
|
70 |
}
|
71 |
|
72 |
+
function purgePost( $_ID )
|
73 |
+
{
|
74 |
global $rt_wp_nginx_helper, $blog_id;
|
75 |
+
if ( !$rt_wp_nginx_helper->options['enable_purge'] ) {
|
76 |
return;
|
77 |
}
|
78 |
switch ( current_filter() ) {
|
108 |
|
109 |
$this->log( "Function purgePost BEGIN ===" );
|
110 |
|
111 |
+
if ( $rt_wp_nginx_helper->options['purge_homepage_on_edit'] == 1 ) {
|
112 |
$homepage_url = trailingslashit( home_url() );
|
113 |
|
114 |
$this->log( "Purging homepage '$homepage_url'" );
|
117 |
|
118 |
|
119 |
if ( current_filter() == 'comment_post' || current_filter() == 'wp_set_comment_status' ) {
|
120 |
+
$this->_purge_by_options( $_ID, $blog_id, $rt_wp_nginx_helper->options['purge_page_on_new_comment'], $rt_wp_nginx_helper->options['purge_archive_on_new_comment'], $rt_wp_nginx_helper->options['purge_archive_on_new_comment'] );
|
121 |
} else {
|
122 |
+
$this->_purge_by_options( $_ID, $blog_id, $rt_wp_nginx_helper->options['purge_page_on_mod'], $rt_wp_nginx_helper->options['purge_archive_on_edit'], $rt_wp_nginx_helper->options['purge_archive_on_edit'] );
|
123 |
}
|
124 |
|
125 |
$this->log( "Function purgePost END ^^^" );
|
126 |
}
|
127 |
|
128 |
+
private function _purge_by_options( $_post_ID, $blog_id, $_purge_page, $_purge_archive, $_purge_custom_taxa )
|
129 |
+
{
|
130 |
global $rt_wp_nginx_helper;
|
131 |
|
132 |
$_post_type = get_post_type( $_post_ID );
|
194 |
$this->log( "Purging custom taxonomies related" );
|
195 |
foreach ( $custom_taxonomies as $taxon ) {
|
196 |
|
197 |
+
if ( !in_array( $taxon, array( 'category', 'post_tag', 'link_category' ) ) ) {
|
198 |
|
199 |
if ( $terms = get_the_terms( $_post_ID, $taxon ) ) {
|
200 |
foreach ( $terms as $term ) {
|
209 |
}
|
210 |
}
|
211 |
|
212 |
+
function purgeUrl( $url, $feed = true )
|
213 |
+
{
|
214 |
global $rt_wp_nginx_helper;
|
215 |
|
216 |
$this->log( "- Purging URL | " . $url );
|
217 |
|
218 |
$parse = parse_url( $url );
|
219 |
+
$host = $rt_wp_nginx_helper->options['redis_hostname'];
|
220 |
+
$prefix = $rt_wp_nginx_helper->options['redis_prefix'];
|
221 |
+
$_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $host . $parse['path'];
|
222 |
+
delete_single_key( $_url_purge_base );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
}
|
224 |
|
225 |
+
function log( $msg, $level = 'INFO' )
|
226 |
+
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
|
228 |
global $rt_wp_nginx_helper;
|
229 |
+
if ( !$rt_wp_nginx_helper->options['enable_log'] ) {
|
230 |
return;
|
231 |
}
|
232 |
|
233 |
$log_levels = array( "INFO" => 0, "WARNING" => 1, "ERROR" => 2, "NONE" => 3 );
|
234 |
|
235 |
+
if ( $log_levels[$level] >= $log_levels[$rt_wp_nginx_helper->options['log_level']] ) {
|
236 |
if ( $fp = fopen( $rt_wp_nginx_helper->functional_asset_path() . 'nginx.log', "a+" ) ) {
|
237 |
fwrite( $fp, "\n" . gmdate( "Y-m-d H:i:s " ) . " | " . $level . " | " . $msg );
|
238 |
fclose( $fp );
|
242 |
return true;
|
243 |
}
|
244 |
|
245 |
+
function checkAndTruncateLogFile()
|
246 |
+
{
|
247 |
|
248 |
global $rt_wp_nginx_helper;
|
249 |
|
250 |
+
$maxSizeAllowed = (is_numeric( $rt_wp_nginx_helper->options['log_filesize'] )) ? $rt_wp_nginx_helper->options['log_filesize'] * 1048576 : 5242880;
|
251 |
|
252 |
$fileSize = filesize( $rt_wp_nginx_helper->functional_asset_path() . 'nginx.log' );
|
253 |
|
268 |
}
|
269 |
}
|
270 |
|
271 |
+
function purgeImageOnEdit( $attachment_id )
|
272 |
+
{
|
273 |
|
274 |
$this->log( "Purging media on edit BEGIN ===" );
|
275 |
|
278 |
$this->purgeUrl( wp_get_attachment_url( $attachment_id ), false );
|
279 |
$attachment = wp_get_attachment_metadata( $attachment_id );
|
280 |
|
281 |
+
if ( !empty( $attachment['sizes'] ) && is_array( $attachment['sizes'] ) ) {
|
282 |
+
foreach ( $attachment['sizes'] as $size_name => $size ) {
|
283 |
$resize_image = wp_get_attachment_image_src( $attachment_id, $size_name );
|
284 |
if ( $resize_image )
|
285 |
+
$this->purgeUrl( $resize_image[0], false );
|
286 |
}
|
287 |
}
|
288 |
$this->purgeURL( get_attachment_link( $attachment_id ) );
|
293 |
$this->log( "Purging media on edit END ^^^" );
|
294 |
}
|
295 |
|
296 |
+
function purge_on_post_moved_to_trash( $new_status, $old_status, $post )
|
297 |
+
{
|
298 |
|
299 |
global $rt_wp_nginx_helper, $blog_id;
|
300 |
+
if ( !$rt_wp_nginx_helper->options['enable_purge'] ) {
|
301 |
return;
|
302 |
}
|
303 |
if ( $new_status == 'trash' ) {
|
310 |
|
311 |
|
312 |
|
313 |
+
if ( $rt_wp_nginx_helper->options['purge_homepage_on_del'] == 1 ) {
|
314 |
$this->_purge_homepage();
|
315 |
}
|
316 |
|
317 |
|
318 |
+
$this->_purge_by_options( $post->ID, $blog_id, false, $rt_wp_nginx_helper->options['purge_archive_on_del'], $rt_wp_nginx_helper->options['purge_archive_on_del'] );
|
319 |
|
320 |
|
321 |
|
325 |
return true;
|
326 |
}
|
327 |
|
328 |
+
private function _purge_homepage()
|
329 |
+
{
|
330 |
|
331 |
$homepage_url = trailingslashit( home_url() );
|
332 |
|
336 |
return true;
|
337 |
}
|
338 |
|
339 |
+
private function _purge_personal_urls()
|
340 |
+
{
|
341 |
|
342 |
global $rt_wp_nginx_helper;
|
343 |
|
344 |
$this->log( __( "Purging personal urls", "nginx-helper" ) );
|
345 |
|
346 |
+
if ( isset( $rt_wp_nginx_helper->options['purgeable_url']['urls'] ) ) {
|
347 |
|
348 |
+
foreach ( $rt_wp_nginx_helper->options['purgeable_url']['urls'] as $u ) {
|
349 |
$this->purgeUrl( $u, false );
|
350 |
}
|
351 |
} else {
|
355 |
return true;
|
356 |
}
|
357 |
|
358 |
+
private function _purge_post_categories( $_post_id )
|
359 |
+
{
|
360 |
|
361 |
$this->log( __( "Purging category archives", "nginx-helper" ) );
|
362 |
|
370 |
return true;
|
371 |
}
|
372 |
|
373 |
+
private function _purge_post_tags( $_post_id )
|
374 |
+
{
|
375 |
|
376 |
$this->log( __( "Purging tags archives", "nginx-helper" ) );
|
377 |
|
385 |
return true;
|
386 |
}
|
387 |
|
388 |
+
private function _purge_post_custom_taxa( $_post_id )
|
389 |
+
{
|
390 |
|
391 |
$this->log( __( "Purging post custom taxonomies related", "nginx-helper" ) );
|
392 |
|
394 |
foreach ( $custom_taxonomies as $taxon ) {
|
395 |
$this->log( sprintf( "+ " . __( "Purging custom taxonomy '%s'", "nginx-helper" ), $taxon ) );
|
396 |
|
397 |
+
if ( !in_array( $taxon, array( 'category', 'post_tag', 'link_category' ) ) ) {
|
398 |
|
399 |
if ( $terms = get_the_terms( $_post_id, $taxon ) ) {
|
400 |
foreach ( $terms as $term ) {
|
412 |
return true;
|
413 |
}
|
414 |
|
415 |
+
private function _purge_all_categories()
|
416 |
+
{
|
417 |
|
418 |
$this->log( __( "Purging all categories", "nginx-helper" ) );
|
419 |
|
430 |
return true;
|
431 |
}
|
432 |
|
433 |
+
private function _purge_all_posttags()
|
434 |
+
{
|
435 |
|
436 |
$this->log( __( "Purging all tags", "nginx-helper" ) );
|
437 |
|
448 |
return true;
|
449 |
}
|
450 |
|
451 |
+
private function _purge_all_customtaxa()
|
452 |
+
{
|
453 |
|
454 |
$this->log( __( "Purging all custom taxonomies", "nginx-helper" ) );
|
455 |
|
458 |
foreach ( $custom_taxonomies as $taxon ) {
|
459 |
$this->log( sprintf( "+ " . __( "Purging custom taxonomy '%s'", "nginx-helper" ), $taxon ) );
|
460 |
|
461 |
+
if ( !in_array( $taxon, array( 'category', 'post_tag', 'link_category' ) ) ) {
|
462 |
|
463 |
if ( $terms = get_terms( $taxon ) ) {
|
464 |
foreach ( $terms as $term ) {
|
476 |
return true;
|
477 |
}
|
478 |
|
479 |
+
private function _purge_all_taxonomies()
|
480 |
+
{
|
481 |
|
482 |
$this->_purge_all_categories();
|
483 |
$this->_purge_all_posttags();
|
486 |
return true;
|
487 |
}
|
488 |
|
489 |
+
private function _purge_all_posts()
|
490 |
+
{
|
491 |
|
492 |
$this->log( __( "Purging all posts, pages and custom post types.", "nginx-helper" ) );
|
493 |
|
509 |
return true;
|
510 |
}
|
511 |
|
512 |
+
private function _purge_all_date_archives()
|
513 |
+
{
|
514 |
|
515 |
$this->log( __( "Purging all date-based archives.", "nginx-helper" ) );
|
516 |
|
523 |
return true;
|
524 |
}
|
525 |
|
526 |
+
private function _purge_all_daily_archives()
|
527 |
+
{
|
528 |
|
529 |
global $wpdb;
|
530 |
|
549 |
}
|
550 |
}
|
551 |
|
552 |
+
private function _purge_all_monthly_archives()
|
553 |
+
{
|
554 |
|
555 |
global $wpdb;
|
556 |
|
575 |
}
|
576 |
}
|
577 |
|
578 |
+
private function _purge_all_yearly_archives()
|
579 |
+
{
|
580 |
|
581 |
global $wpdb;
|
582 |
|
601 |
}
|
602 |
}
|
603 |
|
604 |
+
function purge_them_all()
|
605 |
+
{
|
606 |
|
607 |
$this->log( __( "Let's purge everything!", "nginx-helper" ) );
|
608 |
|
621 |
return true;
|
622 |
}
|
623 |
|
624 |
+
function purge_on_term_taxonomy_edited( $term_id, $tt_id, $taxon )
|
625 |
+
{
|
626 |
|
627 |
$this->log( __( "Term taxonomy edited or deleted", "nginx-helper" ) );
|
628 |
|
637 |
return true;
|
638 |
}
|
639 |
|
640 |
+
function purge_on_check_ajax_referer( $action, $result )
|
641 |
+
{
|
642 |
|
643 |
switch ( $action ) {
|
644 |
case 'save-sidebar-widgets' :
|
656 |
return true;
|
657 |
}
|
658 |
|
659 |
+
function true_purge_all()
|
660 |
+
{
|
661 |
+
global $rt_wp_nginx_helper;
|
662 |
$this->log( "* * * * *" );
|
663 |
$this->log( "* Purged Everything!" );
|
664 |
$this->log( "* * * * *" );
|
665 |
+
delete_multi_keys("*");
|
666 |
}
|
667 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
668 |
}
|
669 |
+
|
670 |
}
|
tests/functional/custom-commands/enableAllPurgeCheckbox.js
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
exports.command = function(url, username, password) {
|
4 |
+
var browser = this;
|
5 |
+
var data = browser.globals;
|
6 |
+
browser
|
7 |
+
.pause(100)
|
8 |
+
.getAttribute('#purge_homepage_on_edit', "checked", function(result) {
|
9 |
+
if (result.value) {
|
10 |
+
console.log('check box is already enabled');
|
11 |
+
} else {
|
12 |
+
|
13 |
+
browser.click('#purge_homepage_on_edit');
|
14 |
+
}
|
15 |
+
})
|
16 |
+
.getAttribute('#purge_homepage_on_del', "checked", function(result) {
|
17 |
+
if (result.value) {
|
18 |
+
console.log('check box is already enabled');
|
19 |
+
} else {
|
20 |
+
browser.click('#purge_homepage_on_del');
|
21 |
+
}
|
22 |
+
})
|
23 |
+
.getAttribute('#purge_page_on_mod', "checked", function(result) {
|
24 |
+
if (result.value) {
|
25 |
+
console.log('check box is already enabled');
|
26 |
+
} else {
|
27 |
+
browser.click('#purge_page_on_mod');
|
28 |
+
}
|
29 |
+
})
|
30 |
+
.getAttribute('#purge_page_on_new_comment', "checked", function(result) {
|
31 |
+
if (result.value) {
|
32 |
+
console.log('check box is already enabled');
|
33 |
+
} else {
|
34 |
+
browser.click('#purge_page_on_new_comment');
|
35 |
+
}
|
36 |
+
})
|
37 |
+
.getAttribute('#purge_page_on_deleted_comment', "checked", function(result) {
|
38 |
+
if (result.value) {
|
39 |
+
console.log('check box is already enabled');
|
40 |
+
} else {
|
41 |
+
browser.click('#purge_page_on_deleted_comment');
|
42 |
+
}
|
43 |
+
})
|
44 |
+
.getAttribute('#purge_archive_on_edit', "checked", function(result) {
|
45 |
+
if (result.value) {
|
46 |
+
console.log('check box is already enabled');
|
47 |
+
} else {
|
48 |
+
browser.click('#purge_archive_on_edit');
|
49 |
+
}
|
50 |
+
})
|
51 |
+
.getAttribute('#purge_archive_on_del', "checked", function(result) {
|
52 |
+
if (result.value) {
|
53 |
+
console.log('check box is already enabled');
|
54 |
+
} else {
|
55 |
+
browser.click('#purge_archive_on_del');
|
56 |
+
}
|
57 |
+
})
|
58 |
+
.getAttribute('#purge_archive_on_new_comment', "checked", function(result) {
|
59 |
+
if (result.value) {
|
60 |
+
console.log('check box is already enabled');
|
61 |
+
} else {
|
62 |
+
browser.click('#purge_archive_on_new_comment');
|
63 |
+
}
|
64 |
+
})
|
65 |
+
.getAttribute('#purge_archive_on_deleted_comment', "checked", function(result) {
|
66 |
+
if (result.value) {
|
67 |
+
console.log('check box is already enabled');
|
68 |
+
} else {
|
69 |
+
browser.click('#purge_archive_on_deleted_comment');
|
70 |
+
}
|
71 |
+
})
|
72 |
+
|
73 |
+
return this;
|
74 |
+
};
|
tests/functional/custom-commands/goToAddNewPage.js
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
exports.command = function(url, username, password) {
|
4 |
+
var client = this;
|
5 |
+
var data = client.globals;
|
6 |
+
var dash = data.URLS.LOGIN + 'wp-admin/post-new.php?post_type=page'
|
7 |
+
client
|
8 |
+
.pause(100)
|
9 |
+
.url(dash)
|
10 |
+
.getTitle(function(title) {
|
11 |
+
console.log("We are in Add new Post Page :" + title);
|
12 |
+
})
|
13 |
+
return this;
|
14 |
+
};
|
tests/functional/custom-commands/goToAddNewPost.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
exports.command = function(url, username, password) {
|
4 |
+
var client = this;
|
5 |
+
var data = client.globals;
|
6 |
+
var dash = data.URLS.LOGIN + 'wp-admin/post-new.php'
|
7 |
+
client
|
8 |
+
.pause(100)
|
9 |
+
.url(dash)
|
10 |
+
.waitForElementVisible('body', 3000)
|
11 |
+
.getTitle(function(title) {
|
12 |
+
console.log("We are in Add new Post Page :" + title);
|
13 |
+
})
|
14 |
+
|
15 |
+
return this;
|
16 |
+
};
|
tests/functional/custom-commands/nginxSettings.js
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
exports.command = function(url, username, password) {
|
4 |
+
var client = this;
|
5 |
+
var data = client.globals;
|
6 |
+
var dash = data.URLS.LOGIN + 'wp-admin/options-general.php?page=nginx'
|
7 |
+
client
|
8 |
+
.pause(100)
|
9 |
+
.url(dash)
|
10 |
+
.getTitle(function(title) {
|
11 |
+
console.log("We are in nginx settings Page :" + title);
|
12 |
+
})
|
13 |
+
|
14 |
+
return this;
|
15 |
+
};
|
tests/functional/custom-commands/wplogin.js
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
exports.command = function(url, username, password) {
|
3 |
+
var client = this;
|
4 |
+
var loginurl = url + "/wp-admin";
|
5 |
+
|
6 |
+
client
|
7 |
+
.url(loginurl)
|
8 |
+
.pause(500)
|
9 |
+
.setValue('input[id="user_login"]', username)
|
10 |
+
.setValue('input[id="user_pass"]', password)
|
11 |
+
.click('input[type=submit]')
|
12 |
+
|
13 |
+
return this;
|
14 |
+
};
|
tests/functional/custom-commands/wplogout.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
|
3 |
+
exports.command = function() {
|
4 |
+
var client = this;
|
5 |
+
|
6 |
+
client
|
7 |
+
.pause(1000)
|
8 |
+
.moveToElement('#wp-admin-bar-my-account a.ab-item',1,1) //move to top RHS
|
9 |
+
.click('#wp-admin-bar-logout a.ab-item')
|
10 |
+
.pause(2000)
|
11 |
+
.getTitle(function(title) {
|
12 |
+
console.log("Logged out..");
|
13 |
+
})
|
14 |
+
|
15 |
+
return this;
|
16 |
+
};
|
tests/functional/nightwatch.json
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"src_folders": ["src"],
|
3 |
+
"custom_commands_path": "./custom-commands",
|
4 |
+
"globals_path": "./res/constants.js",
|
5 |
+
|
6 |
+
"selenium": {
|
7 |
+
"start_process": true,
|
8 |
+
"server_path": "./node_modules/selenium-server/lib/runner/selenium-server-standalone-2.45.0.jar"
|
9 |
+
},
|
10 |
+
|
11 |
+
"test_settings": {
|
12 |
+
"default": {
|
13 |
+
"launch_url": "http://localhost",
|
14 |
+
"selenium_port": 4444,
|
15 |
+
"selenium_host": "localhost",
|
16 |
+
"silent": true,
|
17 |
+
"desiredCapabilities": {
|
18 |
+
"browserName": "phantomjs",
|
19 |
+
"javascriptEnabled": true
|
20 |
+
}
|
21 |
+
}
|
22 |
+
}
|
23 |
+
}
|
tests/functional/package.json
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "nginx-helper-test-suite",
|
3 |
+
"main": "",
|
4 |
+
"author": "Prabuddha Chakraborty",
|
5 |
+
"version": "1.0.0",
|
6 |
+
"keywords": [],
|
7 |
+
"homepage": "",
|
8 |
+
"repository": {
|
9 |
+
"type": "git",
|
10 |
+
"url": ""
|
11 |
+
},
|
12 |
+
"description": "",
|
13 |
+
"contributors": [],
|
14 |
+
"dependencies": {
|
15 |
+
"nightwatch": "~0.4.17"
|
16 |
+
},
|
17 |
+
|
18 |
+
"devDependencies": {
|
19 |
+
"selenium-server": "2.45.0",
|
20 |
+
"phantomjs": "1.9.17"
|
21 |
+
}
|
22 |
+
}
|
tests/functional/res/constants.js
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
@author : Prabuddha Chakraborty
|
3 |
+
This is a standalone scripts for centralized management of all constants like Admin Username and Password.
|
4 |
+
*/
|
5 |
+
|
6 |
+
module.exports ={
|
7 |
+
//username and password for wordpress Admin Role
|
8 |
+
TESTADMINUSERNAME: 'ADMINUSER',
|
9 |
+
TESTADMINPASSWORD: 'ADMINPASS',
|
10 |
+
//Home site url enter here
|
11 |
+
URLS: {
|
12 |
+
LOGIN: 'http://wp.localtest.me/'
|
13 |
+
}
|
14 |
+
|
15 |
+
};
|
tests/functional/src/purge-method-get-request-page-test.js
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
global.urlp;
|
3 |
+
module.exports = {
|
4 |
+
'Step One : Configure nginx-helpers settings from dashboard ': function(browser) {
|
5 |
+
var data = browser.globals;
|
6 |
+
browser
|
7 |
+
.maximizeWindow()
|
8 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
9 |
+
.nginxSettings()
|
10 |
+
.pause(2000)
|
11 |
+
.getAttribute('#enable_purge', "checked", function(result) {
|
12 |
+
if (result.value) {
|
13 |
+
console.log('check box is already enabled');
|
14 |
+
} else {
|
15 |
+
|
16 |
+
browser.click('#enable_purge');
|
17 |
+
browser.click('#smart_http_expire_save');
|
18 |
+
}
|
19 |
+
})
|
20 |
+
.enableAllPurgeCheckbox()
|
21 |
+
.click('#purge_method_get_request')
|
22 |
+
.click('#smart_http_expire_save')
|
23 |
+
.pause(1000)
|
24 |
+
},
|
25 |
+
'step two: Create page & check on frontend test ': function(browser) {
|
26 |
+
var data = browser.globals;
|
27 |
+
browser
|
28 |
+
.goToAddNewPage()
|
29 |
+
.clearValue('#title')
|
30 |
+
.clearValue('textarea[id="content"]')
|
31 |
+
.setValue('#title', 'test-page')
|
32 |
+
.setValue('textarea[id="content"]', "test page created for testing")
|
33 |
+
.pause(1000)
|
34 |
+
.click('#publish')
|
35 |
+
.pause(2000)
|
36 |
+
.getText("#editable-post-name", function(result) {
|
37 |
+
urlp = result.value;
|
38 |
+
console.log(data.URLS.LOGIN + urlp);
|
39 |
+
browser
|
40 |
+
.wplogout()
|
41 |
+
.pause(500)
|
42 |
+
.url(data.URLS.LOGIN + urlp)
|
43 |
+
.assert.containsText("#main", "test page created for testing")
|
44 |
+
|
45 |
+
})
|
46 |
+
|
47 |
+
|
48 |
+
},
|
49 |
+
|
50 |
+
'Step three : Update Content in Page & check on frontend': function(browser) {
|
51 |
+
var data = browser.globals;
|
52 |
+
browser
|
53 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
54 |
+
.url(data.URLS.LOGIN + urlp)
|
55 |
+
.click('.post-edit-link')
|
56 |
+
|
57 |
+
.clearValue('textarea[id="content"]')
|
58 |
+
.setValue('textarea[id="content"]', "test page content updated")
|
59 |
+
.click('#publish')
|
60 |
+
.wplogout()
|
61 |
+
.url(data.URLS.LOGIN + urlp)
|
62 |
+
.assert.containsText("#main", "test page content updated")
|
63 |
+
},
|
64 |
+
|
65 |
+
'Step four : Page comment check ': function(browser) {
|
66 |
+
var data = browser.globals;
|
67 |
+
browser
|
68 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
69 |
+
.url(data.URLS.LOGIN + urlp)
|
70 |
+
.setValue('textarea[name="comment"]', 'this is a demo test comment on page')
|
71 |
+
.click('input[value="Post Comment"]')
|
72 |
+
.assert.containsText("#main", "this is a demo test comment on page")
|
73 |
+
.wplogout()
|
74 |
+
.url(data.URLS.LOGIN + urlp)
|
75 |
+
.assert.containsText("#main", "this is a demo test comment on page")
|
76 |
+
},
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
'Step five : Move to trash test ': function(browser) {
|
81 |
+
var data = browser.globals;
|
82 |
+
browser
|
83 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
84 |
+
.url(data.URLS.LOGIN + urlp)
|
85 |
+
.click('.post-edit-link')
|
86 |
+
.click('xpath', '//a[text()="Move to Trash"]')
|
87 |
+
.pause(2500)
|
88 |
+
.wplogout()
|
89 |
+
.url(data.URLS.LOGIN + urlp);
|
90 |
+
browser.expect.element('#main').text.to.not.contain("this is a demo test comment on page");
|
91 |
+
browser.end();
|
92 |
+
}
|
93 |
+
|
94 |
+
};
|
tests/functional/src/purge-method-get-request-post-test.js
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
module.exports = {
|
4 |
+
'Step One : Configure nginx-helpers settings from dashboard ': function(browser) {
|
5 |
+
var data = browser.globals;
|
6 |
+
browser
|
7 |
+
.maximizeWindow()
|
8 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
9 |
+
.nginxSettings()
|
10 |
+
.pause(2000)
|
11 |
+
.getAttribute('#enable_purge', "checked", function(result) {
|
12 |
+
if (result.value) {
|
13 |
+
console.log('check box is already enabled');
|
14 |
+
} else {
|
15 |
+
browser.click('#enable_purge');
|
16 |
+
browser.click('#smart_http_expire_save');
|
17 |
+
}
|
18 |
+
})
|
19 |
+
.enableAllPurgeCheckbox()
|
20 |
+
.click('#purge_method_get_request')
|
21 |
+
.click('#smart_http_expire_save')
|
22 |
+
.pause(1000)
|
23 |
+
},
|
24 |
+
'step two: Upload post to check ': function(browser) {
|
25 |
+
var data = browser.globals;
|
26 |
+
browser
|
27 |
+
.goToAddNewPost()
|
28 |
+
.clearValue('#title')
|
29 |
+
.clearValue('textarea[id="content"]')
|
30 |
+
.setValue('#title', 'test title for nightwatch test')
|
31 |
+
.setValue('textarea[id="content"]', "test post for nightwatch test")
|
32 |
+
.pause(1000)
|
33 |
+
.click('#publish')
|
34 |
+
.pause(2200)
|
35 |
+
.wplogout()
|
36 |
+
.url(data.URLS.LOGIN)
|
37 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
38 |
+
.assert.containsText("#main", "test post for nightwatch test")
|
39 |
+
},
|
40 |
+
|
41 |
+
'Step three : Update Post Home Page Check ': function(browser) {
|
42 |
+
var data = browser.globals;
|
43 |
+
browser
|
44 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
45 |
+
.url(data.URLS.LOGIN)
|
46 |
+
.click('.post-edit-link')
|
47 |
+
.clearValue('#title')
|
48 |
+
.clearValue('textarea[id="content"]')
|
49 |
+
.setValue('#title', 'test title for nightwatch test')
|
50 |
+
.setValue('textarea[id="content"]', "test post for nightwatch test update")
|
51 |
+
.click('#publish')
|
52 |
+
.wplogout()
|
53 |
+
.url(data.URLS.LOGIN)
|
54 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
55 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
56 |
+
},
|
57 |
+
'Step four : post comment check ': function(browser) {
|
58 |
+
var data = browser.globals;
|
59 |
+
browser
|
60 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
61 |
+
.url(data.URLS.LOGIN)
|
62 |
+
.click('xpath', '//a[text()="test title for nightwatch test"]')
|
63 |
+
.setValue('textarea[name="comment"]', 'this is a demo test comment')
|
64 |
+
.click('input[value="Post Comment"]')
|
65 |
+
.assert.containsText("#main", "this is a demo test comment")
|
66 |
+
.wplogout()
|
67 |
+
},
|
68 |
+
|
69 |
+
'Step five : Update Post check ': function(browser) {
|
70 |
+
var data = browser.globals;
|
71 |
+
browser
|
72 |
+
.url(data.URLS.LOGIN)
|
73 |
+
.click('xpath', '//a[text()="test title for nightwatch test"]')
|
74 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
75 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
76 |
+
.assert.containsText("#main", "this is a demo test comment")
|
77 |
+
},
|
78 |
+
'Step six : Update Archives check (Uncategorized)': function(browser) {
|
79 |
+
var data = browser.globals;
|
80 |
+
browser
|
81 |
+
.url(data.URLS.LOGIN)
|
82 |
+
.click('xpath', '//a[text()="Uncategorized"]')
|
83 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
84 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
85 |
+
},
|
86 |
+
/*
|
87 |
+
'Step seven : Update Archives check (Admin && Date)': function(browser) {
|
88 |
+
var data = browser.globals;
|
89 |
+
browser
|
90 |
+
.url(data.URLS.LOGIN + 'author/admin/')
|
91 |
+
|
92 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
93 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
94 |
+
.url(data.URLS.LOGIN)
|
95 |
+
.click('.entry-date')
|
96 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
97 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
98 |
+
},
|
99 |
+
*/
|
100 |
+
|
101 |
+
'Step eight : Set as trash ': function(browser) {
|
102 |
+
var data = browser.globals;
|
103 |
+
browser
|
104 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
105 |
+
.url(data.URLS.LOGIN)
|
106 |
+
.click('.post-edit-link')
|
107 |
+
.click('xpath', '//a[text()="Move to Trash"]')
|
108 |
+
.pause(2500)
|
109 |
+
.wplogout()
|
110 |
+
.url(data.URLS.LOGIN);
|
111 |
+
browser.expect.element('#main').text.to.not.contain('test title for nightwatch test');
|
112 |
+
browser.expect.element('#main').text.to.not.contain('test post for nightwatch test update');
|
113 |
+
},
|
114 |
+
|
115 |
+
'Step nine : Set as Draft ': function(browser) {
|
116 |
+
var data = browser.globals;
|
117 |
+
browser
|
118 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
119 |
+
.goToAddNewPost()
|
120 |
+
.clearValue('#title')
|
121 |
+
.clearValue('textarea[id="content"]')
|
122 |
+
.setValue('#title', 'test title for draft post test')
|
123 |
+
.setValue('textarea[id="content"]', "test post for draft post test")
|
124 |
+
.pause(1000)
|
125 |
+
.click('#publish')
|
126 |
+
.pause(2000)
|
127 |
+
.url(data.URLS.LOGIN)
|
128 |
+
.assert.containsText("#main", "test title for draft post test")
|
129 |
+
.assert.containsText("#main", "test post for draft post test")
|
130 |
+
.click('.post-edit-link')
|
131 |
+
.click('.edit-post-status')
|
132 |
+
.pause(300)
|
133 |
+
.click('option[value="draft"]')
|
134 |
+
.pause(500)
|
135 |
+
.click('#publish')
|
136 |
+
.pause(2500)
|
137 |
+
.wplogout()
|
138 |
+
.url(data.URLS.LOGIN);
|
139 |
+
browser.expect.element('#main').text.to.not.contain('test title for draft post test');
|
140 |
+
browser.expect.element('#main').text.to.not.contain('test post for draft post test');
|
141 |
+
browser
|
142 |
+
.end()
|
143 |
+
}
|
144 |
+
|
145 |
+
};
|
tests/functional/src/purge-method-unlink-files-page-test.js
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
global.urlp;
|
3 |
+
module.exports = {
|
4 |
+
'Step One : Configure nginx-helpers settings from dashboard ': function(browser) {
|
5 |
+
var data = browser.globals;
|
6 |
+
browser
|
7 |
+
.maximizeWindow()
|
8 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
9 |
+
.nginxSettings()
|
10 |
+
.pause(2000)
|
11 |
+
.getAttribute('#enable_purge', "checked", function(result) {
|
12 |
+
if (result.value) {
|
13 |
+
console.log('check box is already enabled');
|
14 |
+
} else {
|
15 |
+
|
16 |
+
browser.click('#enable_purge');
|
17 |
+
browser.click('#smart_http_expire_save');
|
18 |
+
}
|
19 |
+
})
|
20 |
+
.enableAllPurgeCheckbox()
|
21 |
+
.click('#purge_method_unlink_files')
|
22 |
+
.click('#smart_http_expire_save')
|
23 |
+
.pause(1000)
|
24 |
+
},
|
25 |
+
'step two: Create a page ': function(browser) {
|
26 |
+
var data = browser.globals;
|
27 |
+
browser
|
28 |
+
.goToAddNewPage()
|
29 |
+
.clearValue('#title')
|
30 |
+
.clearValue('textarea[id="content"]')
|
31 |
+
.setValue('#title', 'test-page')
|
32 |
+
.setValue('textarea[id="content"]', "test page created for testing")
|
33 |
+
.pause(1000)
|
34 |
+
.click('#publish')
|
35 |
+
.pause(2000)
|
36 |
+
.getText("#editable-post-name", function(result) {
|
37 |
+
urlp = result.value;
|
38 |
+
console.log(data.URLS.LOGIN + urlp);
|
39 |
+
browser
|
40 |
+
.wplogout()
|
41 |
+
.pause(500)
|
42 |
+
.url(data.URLS.LOGIN + urlp)
|
43 |
+
.assert.containsText("#main", "test page created for testing")
|
44 |
+
|
45 |
+
})
|
46 |
+
|
47 |
+
|
48 |
+
},
|
49 |
+
|
50 |
+
'Step three : Update Content in Page ': function(browser) {
|
51 |
+
var data = browser.globals;
|
52 |
+
browser
|
53 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
54 |
+
.url(data.URLS.LOGIN + urlp)
|
55 |
+
.click('.post-edit-link')
|
56 |
+
|
57 |
+
.clearValue('textarea[id="content"]')
|
58 |
+
.setValue('textarea[id="content"]', "test page content updated")
|
59 |
+
.click('#publish')
|
60 |
+
.wplogout()
|
61 |
+
.url(data.URLS.LOGIN + urlp)
|
62 |
+
.assert.containsText("#main", "test page content updated")
|
63 |
+
},
|
64 |
+
|
65 |
+
'Step four : Page comment check ': function(browser) {
|
66 |
+
var data = browser.globals;
|
67 |
+
browser
|
68 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
69 |
+
.url(data.URLS.LOGIN + urlp)
|
70 |
+
.setValue('textarea[name="comment"]', 'this is a demo test comment on page')
|
71 |
+
.click('input[value="Post Comment"]')
|
72 |
+
.assert.containsText("#main", "this is a demo test comment on page")
|
73 |
+
.wplogout()
|
74 |
+
.url(data.URLS.LOGIN + urlp)
|
75 |
+
.assert.containsText("#main", "this is a demo test comment on page")
|
76 |
+
},
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
'Step five : move to trash ': function(browser) {
|
81 |
+
var data = browser.globals;
|
82 |
+
browser
|
83 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
84 |
+
.url(data.URLS.LOGIN + urlp)
|
85 |
+
.click('.post-edit-link')
|
86 |
+
.click('xpath', '//a[text()="Move to Trash"]')
|
87 |
+
.pause(2500)
|
88 |
+
.wplogout()
|
89 |
+
.url(data.URLS.LOGIN + urlp);
|
90 |
+
browser.expect.element('#main').text.to.not.contain("this is a demo test comment on page");
|
91 |
+
browser.end();
|
92 |
+
}
|
93 |
+
|
94 |
+
};
|
tests/functional/src/purge-method-unlink-files-post-test.js
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* @author: Prabuddha Chakraborty */
|
2 |
+
|
3 |
+
module.exports = {
|
4 |
+
'Step One : Configure nginx-helpers settings from dashboard ': function(browser) {
|
5 |
+
var data = browser.globals;
|
6 |
+
browser
|
7 |
+
.maximizeWindow()
|
8 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
9 |
+
.nginxSettings()
|
10 |
+
.pause(2000)
|
11 |
+
.getAttribute('#enable_purge', "checked", function(result) {
|
12 |
+
if (result.value) {
|
13 |
+
console.log('check box is already enabled');
|
14 |
+
} else {
|
15 |
+
browser.click('#enable_purge');
|
16 |
+
browser.click('#smart_http_expire_save');
|
17 |
+
}
|
18 |
+
})
|
19 |
+
.enableAllPurgeCheckbox()
|
20 |
+
.click('#purge_method_unlink_files')
|
21 |
+
.click('#smart_http_expire_save')
|
22 |
+
.pause(1000)
|
23 |
+
},
|
24 |
+
'step two: Upload post to check ': function(browser) {
|
25 |
+
var data = browser.globals;
|
26 |
+
browser
|
27 |
+
.goToAddNewPost()
|
28 |
+
.clearValue('#title')
|
29 |
+
.clearValue('textarea[id="content"]')
|
30 |
+
.setValue('#title', 'test title for nightwatch test')
|
31 |
+
.setValue('textarea[id="content"]', "test post for nightwatch test")
|
32 |
+
.pause(1000)
|
33 |
+
.click('#publish')
|
34 |
+
.pause(2000)
|
35 |
+
.wplogout()
|
36 |
+
.url(data.URLS.LOGIN)
|
37 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
38 |
+
.assert.containsText("#main", "test post for nightwatch test")
|
39 |
+
},
|
40 |
+
|
41 |
+
'Step three : Update Post Home Page Check ': function(browser) {
|
42 |
+
var data = browser.globals;
|
43 |
+
browser
|
44 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
45 |
+
.url(data.URLS.LOGIN)
|
46 |
+
.click('.post-edit-link')
|
47 |
+
.clearValue('#title')
|
48 |
+
.clearValue('textarea[id="content"]')
|
49 |
+
.setValue('#title', 'test title for nightwatch test')
|
50 |
+
.setValue('textarea[id="content"]', "test post for nightwatch test update")
|
51 |
+
.click('#publish')
|
52 |
+
.wplogout()
|
53 |
+
.url(data.URLS.LOGIN)
|
54 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
55 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
56 |
+
},
|
57 |
+
'Step four : post comment check ': function(browser) {
|
58 |
+
var data = browser.globals;
|
59 |
+
browser
|
60 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
61 |
+
.url(data.URLS.LOGIN)
|
62 |
+
.click('xpath', '//a[text()="test title for nightwatch test"]')
|
63 |
+
.setValue('textarea[name="comment"]', 'this is a demo test comment')
|
64 |
+
.click('input[value="Post Comment"]')
|
65 |
+
.assert.containsText("#main", "this is a demo test comment")
|
66 |
+
.wplogout()
|
67 |
+
},
|
68 |
+
|
69 |
+
'Step five : Update Post check ': function(browser) {
|
70 |
+
var data = browser.globals;
|
71 |
+
browser
|
72 |
+
.url(data.URLS.LOGIN)
|
73 |
+
.click('xpath', '//a[text()="test title for nightwatch test"]')
|
74 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
75 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
76 |
+
.assert.containsText("#main", "this is a demo test comment")
|
77 |
+
},
|
78 |
+
'Step six : Update Archives check (Uncategorized)': function(browser) {
|
79 |
+
var data = browser.globals;
|
80 |
+
browser
|
81 |
+
.url(data.URLS.LOGIN)
|
82 |
+
.click('xpath', '//a[text()="Uncategorized"]')
|
83 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
84 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
85 |
+
},
|
86 |
+
|
87 |
+
/*
|
88 |
+
'Step seven : Update Archives check (Admin && Date)': function(browser) {
|
89 |
+
var data = browser.globals;
|
90 |
+
browser
|
91 |
+
.url(data.URLS.LOGIN + 'author/admin/')
|
92 |
+
|
93 |
+
.assert.containsText("#main", "test title for nightwatch test")
|
94 |
+
.assert.containsText("#main", "test post for nightwatch test update")
|
95 |
+
.url(data.URLS.LOGIN)
|
96 |
+
.click('.entry-date')
|
97 |
+
.verify.containsText("#main", "test title for nightwatch test")
|
98 |
+
.verify.containsText("#main", "test post for nightwatch test update")
|
99 |
+
},
|
100 |
+
*/
|
101 |
+
'Step eight : Set as trash ': function(browser) {
|
102 |
+
var data = browser.globals;
|
103 |
+
browser
|
104 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
105 |
+
.url(data.URLS.LOGIN)
|
106 |
+
.click('.post-edit-link')
|
107 |
+
.click('xpath', '//a[text()="Move to Trash"]')
|
108 |
+
.pause(2500)
|
109 |
+
.wplogout()
|
110 |
+
.url(data.URLS.LOGIN);
|
111 |
+
browser.expect.element('#main').text.to.not.contain('test title for nightwatch test');
|
112 |
+
browser.expect.element('#main').text.to.not.contain('test post for nightwatch test update');
|
113 |
+
},
|
114 |
+
|
115 |
+
'Step nine : Set as Draft ': function(browser) {
|
116 |
+
var data = browser.globals;
|
117 |
+
browser
|
118 |
+
.wplogin(data.URLS.LOGIN, data.TESTADMINUSERNAME, data.TESTADMINPASSWORD)
|
119 |
+
.goToAddNewPost()
|
120 |
+
.clearValue('#title')
|
121 |
+
.clearValue('textarea[id="content"]')
|
122 |
+
.setValue('#title', 'test title for draft post test')
|
123 |
+
.setValue('textarea[id="content"]', "test post for draft post test")
|
124 |
+
.pause(1000)
|
125 |
+
.click('#publish')
|
126 |
+
.pause(2000)
|
127 |
+
.url(data.URLS.LOGIN)
|
128 |
+
.assert.containsText("#main", "test title for draft post test")
|
129 |
+
.assert.containsText("#main", "test post for draft post test")
|
130 |
+
.click('.post-edit-link')
|
131 |
+
.click('.edit-post-status')
|
132 |
+
.pause(300)
|
133 |
+
.click('option[value="draft"]')
|
134 |
+
.pause(500)
|
135 |
+
.click('#publish')
|
136 |
+
.pause(2500)
|
137 |
+
.wplogout()
|
138 |
+
.url(data.URLS.LOGIN);
|
139 |
+
browser.expect.element('#main').text.to.not.contain('test title for draft post test');
|
140 |
+
browser.expect.element('#main').text.to.not.contain('test post for draft post test');
|
141 |
+
browser
|
142 |
+
.end()
|
143 |
+
}
|
144 |
+
|
145 |
+
};
|
trunk/admin/admin.php
DELETED
@@ -1,141 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace rtCamp\WP\Nginx {
|
4 |
-
|
5 |
-
class Admin {
|
6 |
-
|
7 |
-
/**
|
8 |
-
* Holds the values to be used in the fields callbacks
|
9 |
-
*/
|
10 |
-
private $nginx_helper_tabs;
|
11 |
-
|
12 |
-
function __construct() {
|
13 |
-
if ( is_multisite() ) {
|
14 |
-
add_action( 'network_admin_menu', array( &$this, 'add_network_menu' ) );
|
15 |
-
} else {
|
16 |
-
add_action( 'admin_menu', array( &$this, 'add_menu' ) );
|
17 |
-
}
|
18 |
-
|
19 |
-
add_action( 'admin_init', array( $this, 'nginx_admin_page_init' ) );
|
20 |
-
|
21 |
-
/**
|
22 |
-
* Define Tabs
|
23 |
-
*/
|
24 |
-
$this->nginx_helper_tabs = apply_filters( 'rt_nginx_helper_tabs', array(
|
25 |
-
'general' => array(
|
26 |
-
'menu_title' => __( 'General', 'nginx-helper' ),
|
27 |
-
'menu_slug' => 'general'
|
28 |
-
),
|
29 |
-
'support' => array(
|
30 |
-
'menu_title' => __( 'Support', 'nginx-helper' ),
|
31 |
-
'menu_slug' => 'support'
|
32 |
-
) )
|
33 |
-
);
|
34 |
-
}
|
35 |
-
|
36 |
-
/**
|
37 |
-
* Add setting sub-menu for single site
|
38 |
-
*/
|
39 |
-
function add_menu() {
|
40 |
-
add_submenu_page( 'options-general.php', __( 'Nginx Helper', 'nginx-helper' ), __( 'Nginx Helper', 'nginx-helper' ), 'manage_options', 'nginx', array( &$this, 'nginx_create_admin_page' ) );
|
41 |
-
}
|
42 |
-
|
43 |
-
/**
|
44 |
-
* Add setting sub-menu for multi site
|
45 |
-
*/
|
46 |
-
function add_network_menu() {
|
47 |
-
add_submenu_page( 'settings.php', __( 'Nginx Helper', 'nginx-helper' ), __( 'Nginx Helper', 'nginx-helper' ), 'manage_options', 'nginx', array( &$this, 'nginx_create_admin_page' ) );
|
48 |
-
}
|
49 |
-
|
50 |
-
/**
|
51 |
-
* Create tab with links
|
52 |
-
*
|
53 |
-
* @param type $current current tab
|
54 |
-
*/
|
55 |
-
function nginx_admin_page_tabs( $current = 'general' ) {
|
56 |
-
echo '<h2 class="nav-tab-wrapper">';
|
57 |
-
foreach ( $this->nginx_helper_tabs as $tab => $name ) {
|
58 |
-
$class = ( $tab == $current ) ? ' nav-tab-active' : '';
|
59 |
-
echo '<a class="nav-tab' . $class . '" href="?page=nginx&tab=' . $name['menu_slug'] . '">' . $name['menu_title'] . '</a>';
|
60 |
-
}
|
61 |
-
echo '</h2>';
|
62 |
-
}
|
63 |
-
|
64 |
-
/**
|
65 |
-
* Options page callback
|
66 |
-
*/
|
67 |
-
function nginx_create_admin_page() {
|
68 |
-
global $pagenow;
|
69 |
-
|
70 |
-
/**
|
71 |
-
* Includes PHP files located in 'admin/lib/' folder
|
72 |
-
*/
|
73 |
-
foreach (glob(plugin_dir_path(__FILE__) . "lib/*.php") as $lib_filename) {
|
74 |
-
require_once( $lib_filename );
|
75 |
-
} ?>
|
76 |
-
|
77 |
-
<div class="wrap rt-nginx-wrapper">
|
78 |
-
<h2 class="rt_option_title"><?php _e( 'Nginx Settings', 'nginx-helper' ); ?></h2>
|
79 |
-
<div id="poststuff">
|
80 |
-
<div id="post-body" class="metabox-holder columns-2">
|
81 |
-
<div id="post-body-content"><?php
|
82 |
-
|
83 |
-
/* Show Tabs */
|
84 |
-
if ( ( 'options-general.php' == $pagenow || 'settings.php' == $pagenow ) && isset( $_GET['tab'] ) ) {
|
85 |
-
$this->nginx_admin_page_tabs( $_GET['tab'] );
|
86 |
-
} else {
|
87 |
-
$this->nginx_admin_page_tabs( 'general' );
|
88 |
-
}
|
89 |
-
|
90 |
-
/* Fetch Page Content */
|
91 |
-
$current = isset( $_GET['tab'] ) ? $_GET['tab'] : 'general';
|
92 |
-
if ( ( 'options-general.php' == $pagenow || 'settings.php' == $pagenow ) && isset( $_GET['page'] ) ) {
|
93 |
-
switch ( $current ) {
|
94 |
-
case 'general' :
|
95 |
-
nginx_general_options_page();
|
96 |
-
break;
|
97 |
-
case 'support' :
|
98 |
-
nginx_support_options_page();
|
99 |
-
break;
|
100 |
-
}
|
101 |
-
} ?>
|
102 |
-
</div> <!-- End of #post-body-content -->
|
103 |
-
<div id="postbox-container-1" class="postbox-container"><?php
|
104 |
-
default_admin_sidebar(); ?>
|
105 |
-
</div> <!-- End of #postbox-container-1 -->
|
106 |
-
</div> <!-- End of #post-body -->
|
107 |
-
</div> <!-- End of #poststuff -->
|
108 |
-
</div> <!-- End of .wrap .rt-nginx-wrapper -->
|
109 |
-
<?php
|
110 |
-
}
|
111 |
-
|
112 |
-
function nginx_admin_page_init() {
|
113 |
-
add_action( 'admin_enqueue_scripts', array( $this, 'nginx_admin_enqueue_assets' ), 999 );
|
114 |
-
add_action( 'admin_bar_menu', array( &$this, 'nginx_toolbar_purge_item' ), 100 );
|
115 |
-
}
|
116 |
-
|
117 |
-
function nginx_toolbar_purge_item( $admin_bar ) {
|
118 |
-
if ( !current_user_can( 'manage_options' ) ) {
|
119 |
-
return;
|
120 |
-
}
|
121 |
-
$purge_url = esc_url( add_query_arg( array( 'nginx_helper_action' => 'purge', 'nginx_helper_urls' => 'all' ) ) );
|
122 |
-
$nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' );
|
123 |
-
$admin_bar->add_menu( array( 'id' => 'nginx-helper-purge-all', 'title' => __( 'Purge Cache', 'nginx-helper' ), 'href' => $nonced_url, 'meta' => array( 'title' => __( 'Purge Cache', 'nginx-helper' ), ), ) );
|
124 |
-
}
|
125 |
-
|
126 |
-
function nginx_admin_enqueue_assets($hook) {
|
127 |
-
if ( 'settings_page_nginx' != $hook ) {
|
128 |
-
return;
|
129 |
-
}
|
130 |
-
|
131 |
-
/* Load Plugin CSS */
|
132 |
-
wp_enqueue_style('rt-nginx-admin-icon', plugin_dir_url(__FILE__) . 'assets/nginx-helper-icons/css/nginx-fontello.css');
|
133 |
-
wp_enqueue_style('rt-nginx-admin-css', plugin_dir_url(__FILE__) . 'assets/style.css');
|
134 |
-
|
135 |
-
/* Load Plugin Scripts */
|
136 |
-
$admin_js = trailingslashit( site_url() ) . '?get_feeds=1';
|
137 |
-
wp_enqueue_script( 'nginx-js', plugin_dir_url( __FILE__ ) . 'assets/nginx.js', '', '', true );
|
138 |
-
wp_localize_script( 'nginx-js', 'news_url', $admin_js );
|
139 |
-
}
|
140 |
-
}
|
141 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/assets/logo.png
DELETED
Binary file
|
trunk/admin/assets/nginx-helper-icons/config.json
DELETED
@@ -1,34 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "nginx-fontello",
|
3 |
-
"css_prefix_text": "nginx-helper-",
|
4 |
-
"css_use_suffix": false,
|
5 |
-
"hinting": true,
|
6 |
-
"units_per_em": 1000,
|
7 |
-
"ascent": 850,
|
8 |
-
"glyphs": [
|
9 |
-
{
|
10 |
-
"uid": "72b1277834cba5b7944b0a6cac7ddb0d",
|
11 |
-
"css": "rss",
|
12 |
-
"code": 59395,
|
13 |
-
"src": "fontawesome"
|
14 |
-
},
|
15 |
-
{
|
16 |
-
"uid": "627abcdb627cb1789e009c08e2678ef9",
|
17 |
-
"css": "twitter",
|
18 |
-
"code": 59394,
|
19 |
-
"src": "fontawesome"
|
20 |
-
},
|
21 |
-
{
|
22 |
-
"uid": "bc50457410acf467b8b5721240768742",
|
23 |
-
"css": "facebook",
|
24 |
-
"code": 59393,
|
25 |
-
"src": "entypo"
|
26 |
-
},
|
27 |
-
{
|
28 |
-
"uid": "b945f4ac2439565661e8e4878e35d379",
|
29 |
-
"css": "gplus",
|
30 |
-
"code": 59392,
|
31 |
-
"src": "entypo"
|
32 |
-
}
|
33 |
-
]
|
34 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/assets/nginx-helper-icons/css/nginx-fontello.css
DELETED
@@ -1,56 +0,0 @@
|
|
1 |
-
@font-face {
|
2 |
-
font-family: 'nginx-fontello';
|
3 |
-
src: url('../font/nginx-fontello.eot?7388141');
|
4 |
-
src: url('../font/nginx-fontello.eot?7388141#iefix') format('embedded-opentype'),
|
5 |
-
url('../font/nginx-fontello.woff?7388141') format('woff'),
|
6 |
-
url('../font/nginx-fontello.ttf?7388141') format('truetype'),
|
7 |
-
url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
|
8 |
-
font-weight: normal;
|
9 |
-
font-style: normal;
|
10 |
-
}
|
11 |
-
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
12 |
-
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
|
13 |
-
/*
|
14 |
-
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
15 |
-
@font-face {
|
16 |
-
font-family: 'nginx-fontello';
|
17 |
-
src: url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
|
18 |
-
}
|
19 |
-
}
|
20 |
-
*/
|
21 |
-
|
22 |
-
[class^="nginx-helper-"]:before, [class*=" nginx-helper-"]:before {
|
23 |
-
font-family: "nginx-fontello";
|
24 |
-
font-style: normal;
|
25 |
-
font-weight: normal;
|
26 |
-
speak: none;
|
27 |
-
|
28 |
-
display: inline-block;
|
29 |
-
text-decoration: inherit;
|
30 |
-
width: 1em;
|
31 |
-
margin-right: .2em;
|
32 |
-
text-align: center;
|
33 |
-
/* opacity: .8; */
|
34 |
-
|
35 |
-
/* For safety - reset parent styles, that can break glyph codes*/
|
36 |
-
font-variant: normal;
|
37 |
-
text-transform: none;
|
38 |
-
|
39 |
-
/* fix buttons height, for twitter bootstrap */
|
40 |
-
line-height: 1em;
|
41 |
-
|
42 |
-
/* Animation center compensation - margins should be symmetric */
|
43 |
-
/* remove if not needed */
|
44 |
-
margin-left: .2em;
|
45 |
-
|
46 |
-
/* you can be more comfortable with increased icons size */
|
47 |
-
/* font-size: 120%; */
|
48 |
-
|
49 |
-
/* Uncomment for 3D effect */
|
50 |
-
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
51 |
-
}
|
52 |
-
|
53 |
-
.nginx-helper-rss:before { content: '\e803'; } /* '' */
|
54 |
-
.nginx-helper-twitter:before { content: '\e802'; } /* '' */
|
55 |
-
.nginx-helper-facebook:before { content: '\e801'; } /* '' */
|
56 |
-
.nginx-helper-gplus:before { content: '\e800'; } /* '' */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.eot
DELETED
Binary file
|
trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.svg
DELETED
@@ -1,15 +0,0 @@
|
|
1 |
-
<?xml version="1.0" standalone="no"?>
|
2 |
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3 |
-
<svg xmlns="http://www.w3.org/2000/svg">
|
4 |
-
<metadata>Copyright (C) 2013 by original authors @ fontello.com</metadata>
|
5 |
-
<defs>
|
6 |
-
<font id="nginx-fontello" horiz-adv-x="1000" >
|
7 |
-
<font-face font-family="nginx-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
8 |
-
<missing-glyph horiz-adv-x="1000" />
|
9 |
-
<glyph glyph-name="rss" unicode="" d="m214 100q0-45-31-76t-76-31t-76 31t-31 76t31 76t76 31t76-31t31-76z m286-69q1-15-9-26q-11-12-27-12h-75q-14 0-24 9t-11 23q-12 128-103 219t-219 103q-14 1-23 11t-9 24v75q0 16 12 26q9 10 24 10h3q89-7 170-45t145-101q63-63 101-145t45-171z m286-1q1-15-10-26q-10-11-26-11h-80q-14 0-25 10t-11 23q-6 120-56 228t-129 188t-188 129t-227 57q-14 0-24 11t-10 24v80q0 15 11 26q10 10 25 10h1q147-8 280-67t238-164q104-104 164-238t67-280z" horiz-adv-x="785.7" />
|
10 |
-
<glyph glyph-name="twitter" unicode="" d="m904 622q-37-54-90-93q0-8 0-23q0-73-21-145t-64-139t-103-117t-144-82t-181-30q-151 0-276 81q19-3 43-3q126 0 224 77q-59 2-105 36t-64 89q19-2 34-2q24 0 48 6q-63 13-104 62t-41 115v2q38-21 82-23q-37 25-59 64t-22 86q0 49 25 91q68-83 164-133t208-55q-5 21-5 41q0 75 53 127t127 53q79 0 132-57q61 12 114 44q-20-64-79-100q52 6 104 28z" horiz-adv-x="928.6" />
|
11 |
-
<glyph glyph-name="facebook" unicode="" d="m500 644l-142 0q-14 0-25-15t-11-37l0-102l178 0l0-148l-178 0l0-442l-170 0l0 442l-152 0l0 148l152 0l0 86q0 94 59 159t147 65l142 0l0-156z" horiz-adv-x="500" />
|
12 |
-
<glyph glyph-name="gplus" unicode="" d="m48 572q0 58 25 102t56 65t69 34t56 15t26 2l230 0l0-4q0-22-78-36q-28 0-38-6q40-20 54-56t14-96q0-102-68-158q-38-38-38-54q0-18 50-64q104-90 104-178q0-140-116-194q-68-34-150-34l-4 0l-4 2q-2-2-4-2q-24 0-54 5t-75 21t-74 57t-29 103q0 60 32 101t83 57t88 22t71 6l2 0q-16 22-24 47t-8 39l2 14l-14 0q-64 0-110 30q-74 44-74 160z m370-452q-4 52-43 84t-103 32l-16 0q-64-2-114-46q-46-42-42-94t53-80t119-24q68 4 109 40t37 88z m-60 500q-30 108-122 108q-12 0-20-2q-40-12-58-62q-16-50-2-106q14-52 47-85t71-33q12 0 18 2q42 12 63 65t3 113z m388-174l150 0l0-94l-150 0l0-150l-94 0l0 150l-150 0l0 94l150 0l0 150l94 0l0-150z" horiz-adv-x="896" />
|
13 |
-
</font>
|
14 |
-
</defs>
|
15 |
-
</svg>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.ttf
DELETED
Binary file
|
trunk/admin/assets/nginx-helper-icons/font/nginx-fontello.woff
DELETED
Binary file
|
trunk/admin/assets/nginx-icon-32x32.png
DELETED
Binary file
|
trunk/admin/assets/nginx.js
DELETED
@@ -1,26 +0,0 @@
|
|
1 |
-
jQuery(document).ready(function() {
|
2 |
-
var news_section = jQuery('#latest_news');
|
3 |
-
if (news_section.length > 0) {
|
4 |
-
jQuery.get(news_url, function(data) {
|
5 |
-
news_section.find('.inside').html(data);
|
6 |
-
});
|
7 |
-
}
|
8 |
-
|
9 |
-
/**
|
10 |
-
* Show OR Hide options on option checkbox
|
11 |
-
* @param {type} selector Selector of Checkbox and PostBox
|
12 |
-
*/
|
13 |
-
function nginx_show_option( selector ) {
|
14 |
-
jQuery( '#'+selector ).on( 'change', function (){
|
15 |
-
if ( jQuery(this).is( ':checked' ) ) {
|
16 |
-
jQuery( '.'+selector ).show();
|
17 |
-
} else {
|
18 |
-
jQuery( '.'+selector ).hide();
|
19 |
-
}
|
20 |
-
} );
|
21 |
-
}
|
22 |
-
/* Function call with parameter */
|
23 |
-
nginx_show_option( 'enable_purge' );
|
24 |
-
nginx_show_option( 'enable_map' );
|
25 |
-
nginx_show_option( 'enable_log' );
|
26 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/assets/rtp-social-icons-32-32.png
DELETED
Binary file
|
trunk/admin/assets/style.css
DELETED
@@ -1,21 +0,0 @@
|
|
1 |
-
.clearfix { *zoom: 1; }
|
2 |
-
.clearfix:before, .clearfix:after { content: " "; display: table; }
|
3 |
-
.clearfix:after { clear: both; }
|
4 |
-
h4 { margin: 0; }
|
5 |
-
.form-table th, .form-wrap label { vertical-align: middle; }
|
6 |
-
table.rtnginx-table { border-bottom: 1px solid #EEE; }
|
7 |
-
table.rtnginx-table:last-child { border-bottom: 0; }
|
8 |
-
.rtnginx-table p.error { color: red; }
|
9 |
-
pre#map { background: #e5e5e5 none; border-radius: 10px; padding: 10px; }
|
10 |
-
.wrap h2.rt_option_title { background: url(nginx-icon-32x32.png) 0 6px no-repeat rgba(0, 0, 0, 0); padding-left: 40px; }
|
11 |
-
#poststuff h2 { padding: 0 0 0 10px; margin-top: 0; }
|
12 |
-
form#purgeall .button-primary { box-shadow: inset 0 -2px rgba(0, 0, 0, 0.14);padding: 15px 30px;font-size: 1rem;border: 0;border-radius: 5px;color: #FFF;background: #DD3D36; height: auto; }
|
13 |
-
form#purgeall .button-primary:hover, form#purgeall .button-primary:focus { background: #d52c24; }
|
14 |
-
.nh-aligncenter { display: block; text-align: center; line-height: 2; }
|
15 |
-
#latest_news .inside ul, #useful-links .inside ul { margin: 0 0 0 12px }
|
16 |
-
#latest_news .inside ul li, #useful-links .inside ul li { list-style: square; padding: 0 0 7px; }
|
17 |
-
#social .inside a { background-color: #666;color: #FFF;display: inline-block;height: 30px;font-size: 1.25rem;line-height: 30px;margin: 10px 20px 0 0;overflow: hidden;padding: 0;text-align: center;text-decoration: none;width: 30px;-webkit-border-radius: 1000px;-moz-border-radius: 1000px;border-radius: 1000px; }
|
18 |
-
#social .inside .nginx-helper-rss:hover { background-color: #FAA33D; }
|
19 |
-
#social .inside .nginx-helper-facebook:hover { background-color: #537BBD; }
|
20 |
-
#social .inside .nginx-helper-twitter:hover { background-color: #40BFF5; }
|
21 |
-
#social .inside .nginx-helper-gplus:hover { background-color: #DD4B39; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/install.php
DELETED
@@ -1,98 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* @author: Saurabh Shukla <saurabh.shukla@rtcamp.com>
|
4 |
-
*
|
5 |
-
* Parts of code based off http://wordpress.org/extend/plugins/nginx-manager/ by http://profiles.wordpress.org/hpatoio/ and http://profiles.wordpress.org/rukbat/
|
6 |
-
*/
|
7 |
-
namespace rtCamp\WP\Nginx {
|
8 |
-
if ( preg_match( '#' . basename( __FILE__ ) . '#', $_SERVER[ 'PHP_SELF' ] ) ) {
|
9 |
-
die( 'You are not allowed to call this page directly.' );
|
10 |
-
}
|
11 |
-
|
12 |
-
function rt_wp_nginx_helper_install() {
|
13 |
-
global $wp_roles, $rt_wp_nginx_helper;
|
14 |
-
|
15 |
-
if ( ! current_user_can( 'activate_plugins' ) ) {
|
16 |
-
return;
|
17 |
-
}
|
18 |
-
|
19 |
-
$role = get_role( 'administrator' );
|
20 |
-
|
21 |
-
if ( empty( $role ) ) {
|
22 |
-
update_site_option( "rt_wp_nginx_helper_init_check", __( 'Sorry, you need to be an administrator to use Nginx Helper', 'nginx-helper' ) );
|
23 |
-
return;
|
24 |
-
}
|
25 |
-
|
26 |
-
$role->add_cap( 'Nginx Helper | Config' );
|
27 |
-
$role->add_cap( 'Nginx Helper | Purge cache' );
|
28 |
-
|
29 |
-
$rt_wp_nginx_helper_get_options = get_site_option( 'rt_wp_nginx_helper_global_options' );
|
30 |
-
|
31 |
-
if ( empty( $rt_wp_nginx_helper_get_options ) ) {
|
32 |
-
$rt_wp_nginx_helper_get_options = rt_wp_nginx_helper_get_options();
|
33 |
-
update_site_option( "rt_wp_nginx_helper_global_options", $rt_wp_nginx_helper_get_options );
|
34 |
-
}
|
35 |
-
|
36 |
-
if ( is_multisite() ) {
|
37 |
-
$blogs = get_blogs_of_user( true );
|
38 |
-
foreach ( $blogs as $b ) {
|
39 |
-
$rt_wp_nginx_helper_options = get_blog_option( $b->userblog_id, 'rt_wp_nginx_helper_options' );
|
40 |
-
if ( empty( $rt_wp_nginx_helper_options ) ) {
|
41 |
-
$rt_wp_nginx_helper_options = rt_wp_nginx_helper_get_options();
|
42 |
-
update_blog_option( $b->userblog_id, "rt_wp_nginx_helper_options", $rt_wp_nginx_helper_options );
|
43 |
-
}
|
44 |
-
}
|
45 |
-
} else {
|
46 |
-
$rt_wp_nginx_helper_options = get_option( 'rt_wp_nginx_helper_options' );
|
47 |
-
if ( empty( $rt_wp_nginx_helper_options ) ) {
|
48 |
-
$rt_wp_nginx_helper_options = rt_wp_nginx_helper_get_options();
|
49 |
-
update_option( "rt_wp_nginx_helper_options", $rt_wp_nginx_helper_options );
|
50 |
-
}
|
51 |
-
}
|
52 |
-
wp_schedule_event( time(), 'daily', 'rt_wp_nginx_helper_check_log_file_size_daily' );
|
53 |
-
}
|
54 |
-
|
55 |
-
function rt_wp_nginx_helper_uninstall() {
|
56 |
-
wp_clear_scheduled_hook( 'rt_wp_nginx_helper_check_log_file_size_daily' );
|
57 |
-
delete_site_option( 'rt_wp_nginx_helper_options' );
|
58 |
-
rt_wp_nginx_helper_remove_capability( 'Nginx Helper | Config' );
|
59 |
-
rt_wp_nginx_helper_remove_capability( 'Nginx Helper | Purge cache' );
|
60 |
-
}
|
61 |
-
|
62 |
-
function rt_wp_nginx_helper_remove_capability( $capability ) {
|
63 |
-
$check_order = array( "subscriber", "contributor", "author", "editor", "administrator" );
|
64 |
-
|
65 |
-
foreach ( $check_order as $role ) {
|
66 |
-
$role = get_role( $role );
|
67 |
-
$role->remove_cap( $capability );
|
68 |
-
}
|
69 |
-
}
|
70 |
-
|
71 |
-
function rt_wp_nginx_helper_get_options() {
|
72 |
-
$rt_wp_nginx_helper_get_options = array( );
|
73 |
-
$rt_wp_nginx_helper_get_options[ 'log_level' ] = 'INFO';
|
74 |
-
$rt_wp_nginx_helper_get_options[ 'log_filesize' ] = 5;
|
75 |
-
|
76 |
-
$rt_wp_nginx_helper_get_options[ 'enable_purge' ] = 0;
|
77 |
-
$rt_wp_nginx_helper_get_options[ 'enable_map' ] = 0;
|
78 |
-
$rt_wp_nginx_helper_get_options[ 'enable_log' ] = 0;
|
79 |
-
$rt_wp_nginx_helper_get_options[ 'enable_stamp' ] = 0;
|
80 |
-
|
81 |
-
$rt_wp_nginx_helper_get_options[ 'purge_homepage_on_new' ] = 1;
|
82 |
-
$rt_wp_nginx_helper_get_options[ 'purge_homepage_on_edit' ] = 1;
|
83 |
-
$rt_wp_nginx_helper_get_options[ 'purge_homepage_on_del' ] = 1;
|
84 |
-
|
85 |
-
$rt_wp_nginx_helper_get_options[ 'purge_archive_on_new' ] = 1;
|
86 |
-
$rt_wp_nginx_helper_get_options[ 'purge_archive_on_edit' ] = 1;
|
87 |
-
$rt_wp_nginx_helper_get_options[ 'purge_archive_on_del' ] = 1;
|
88 |
-
|
89 |
-
$rt_wp_nginx_helper_get_options[ 'purge_archive_on_new_comment' ] = 0;
|
90 |
-
$rt_wp_nginx_helper_get_options[ 'purge_archive_on_deleted_comment' ] = 0;
|
91 |
-
|
92 |
-
$rt_wp_nginx_helper_get_options[ 'purge_page_on_mod' ] = 1;
|
93 |
-
$rt_wp_nginx_helper_get_options[ 'purge_page_on_new_comment' ] = 1;
|
94 |
-
$rt_wp_nginx_helper_get_options[ 'purge_page_on_deleted_comment' ] = 1;
|
95 |
-
|
96 |
-
return $rt_wp_nginx_helper_get_options;
|
97 |
-
}
|
98 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/lib/nginx-general.php
DELETED
@@ -1,374 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace rtCamp\WP\Nginx {
|
4 |
-
|
5 |
-
function nginx_general_options_page() {
|
6 |
-
global $rt_wp_nginx_helper, $rt_wp_nginx_purger;
|
7 |
-
|
8 |
-
$update = 0;
|
9 |
-
$error_time = false;
|
10 |
-
$error_log_filesize = false;
|
11 |
-
$rt_wp_nginx_helper->options['enable_purge'] = (isset( $_POST['enable_purge'] ) and ( $_POST['enable_purge'] == 1) ) ? 1 : 0;
|
12 |
-
$rt_wp_nginx_helper->options['enable_map'] = (isset( $_POST['enable_map'] ) and ( $_POST['enable_map'] == 1) ) ? 1 : 0;
|
13 |
-
$rt_wp_nginx_helper->options['enable_log'] = (isset( $_POST['enable_log'] ) and ( $_POST['enable_log'] == 1) ) ? 1 : 0;
|
14 |
-
$rt_wp_nginx_helper->options['enable_stamp'] = (isset( $_POST['enable_stamp'] ) and ( $_POST['enable_stamp'] == 1) ) ? 1 : 0;
|
15 |
-
|
16 |
-
if ( isset( $_POST['is_submit'] ) && ( $_POST['is_submit'] == 1 ) ) {
|
17 |
-
if ( !(!is_network_admin() && is_multisite() ) ) {
|
18 |
-
if ( $rt_wp_nginx_helper->options['enable_log'] ) {
|
19 |
-
if ( isset( $_POST['log_level'] ) && !empty( $_POST['log_level'] ) && $_POST['log_level'] != '' ) {
|
20 |
-
$rt_wp_nginx_helper->options['log_level'] = $_POST['log_level'];
|
21 |
-
} else {
|
22 |
-
$rt_wp_nginx_helper->options['log_level'] = 'INFO';
|
23 |
-
}
|
24 |
-
if ( isset( $_POST['log_filesize'] ) && !empty( $_POST['log_filesize'] ) && $_POST['log_filesize'] != '' ) {
|
25 |
-
if ( (!is_numeric( $_POST['log_filesize'] ) ) || ( empty( $_POST['log_filesize'] ) ) ) {
|
26 |
-
$error_log_filesize = __( 'Log file size must be a number', 'nginx-helper' );
|
27 |
-
} else {
|
28 |
-
$rt_wp_nginx_helper->options['log_filesize'] = $_POST['log_filesize'];
|
29 |
-
}
|
30 |
-
} else {
|
31 |
-
$rt_wp_nginx_helper->options['log_filesize'] = 5;
|
32 |
-
}
|
33 |
-
}
|
34 |
-
if ( $rt_wp_nginx_helper->options['enable_map'] ) {
|
35 |
-
$rt_wp_nginx_helper->update_map();
|
36 |
-
}
|
37 |
-
}
|
38 |
-
if ( isset( $_POST['enable_purge'] ) ) {
|
39 |
-
$rt_wp_nginx_helper->options['purge_homepage_on_edit'] = ( isset( $_POST['purge_homepage_on_edit'] ) and ( $_POST['purge_homepage_on_edit'] == 1 ) ) ? 1 : 0;
|
40 |
-
$rt_wp_nginx_helper->options['purge_homepage_on_del'] = ( isset( $_POST['purge_homepage_on_del'] ) and ( $_POST['purge_homepage_on_del'] == 1 ) ) ? 1 : 0;
|
41 |
-
|
42 |
-
$rt_wp_nginx_helper->options['purge_archive_on_edit'] = ( isset( $_POST['purge_archive_on_edit'] ) and ( $_POST['purge_archive_on_edit'] == 1 ) ) ? 1 : 0;
|
43 |
-
$rt_wp_nginx_helper->options['purge_archive_on_del'] = ( isset( $_POST['purge_archive_on_del'] ) and ( $_POST['purge_archive_on_del'] == 1 ) ) ? 1 : 0;
|
44 |
-
|
45 |
-
$rt_wp_nginx_helper->options['purge_archive_on_new_comment'] = ( isset( $_POST['purge_archive_on_new_comment'] ) and ( $_POST['purge_archive_on_new_comment'] == 1 ) ) ? 1 : 0;
|
46 |
-
$rt_wp_nginx_helper->options['purge_archive_on_deleted_comment'] = ( isset( $_POST['purge_archive_on_deleted_comment'] ) and ( $_POST['purge_archive_on_deleted_comment'] == 1 ) ) ? 1 : 0;
|
47 |
-
|
48 |
-
$rt_wp_nginx_helper->options['purge_page_on_mod'] = ( isset( $_POST['purge_page_on_mod'] ) and ( $_POST['purge_page_on_mod'] == 1 ) ) ? 1 : 0;
|
49 |
-
$rt_wp_nginx_helper->options['purge_page_on_new_comment'] = ( isset( $_POST['purge_page_on_new_comment'] ) and ( $_POST['purge_page_on_new_comment'] == 1 ) ) ? 1 : 0;
|
50 |
-
$rt_wp_nginx_helper->options['purge_page_on_deleted_comment'] = ( isset( $_POST['purge_page_on_deleted_comment'] ) and ( $_POST['purge_page_on_deleted_comment'] == 1 ) ) ? 1 : 0;
|
51 |
-
|
52 |
-
$rt_wp_nginx_helper->options['purge_method'] = ( isset( $_POST['purge_method'] ) ) ? $_POST['purge_method'] : 'get_request';
|
53 |
-
}
|
54 |
-
update_site_option( 'rt_wp_nginx_helper_options', $rt_wp_nginx_helper->options );
|
55 |
-
$update = 1;
|
56 |
-
}
|
57 |
-
$rt_wp_nginx_helper->options = get_site_option( 'rt_wp_nginx_helper_options' );
|
58 |
-
|
59 |
-
/**
|
60 |
-
* Show Update Message
|
61 |
-
*/
|
62 |
-
if ( isset( $_POST['smart_http_expire_save'] ) ) {
|
63 |
-
echo '<div class="updated"><p>' . __( 'Settings saved.', 'nginx-helper' ) . '</p></div>';
|
64 |
-
}
|
65 |
-
|
66 |
-
/**
|
67 |
-
* Check for single multiple with subdomain OR multiple with subdirectory site
|
68 |
-
*/
|
69 |
-
$nginx_setting_link = '#';
|
70 |
-
if ( is_multisite() ) {
|
71 |
-
if ( SUBDOMAIN_INSTALL == false ) {
|
72 |
-
$nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/multisite/subdirectories/fastcgi-cache-with-purging/';
|
73 |
-
} else {
|
74 |
-
$nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/multisite/subdomains/fastcgi-cache-with-purging/';
|
75 |
-
}
|
76 |
-
} else {
|
77 |
-
$nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/single-site/fastcgi-cache-with-purging/';
|
78 |
-
}
|
79 |
-
?>
|
80 |
-
<div class="postbox">
|
81 |
-
<h3 class="hndle">
|
82 |
-
<span><?php _e( 'Purge Cache', 'nginx-helper' ); ?></span>
|
83 |
-
</h3>
|
84 |
-
<form id="purgeall" action="" method="post" class="clearfix">
|
85 |
-
<div class="inside">
|
86 |
-
<?php $purge_url = add_query_arg( array( 'nginx_helper_action' => 'purge', 'nginx_helper_urls' => 'all' ) ); ?>
|
87 |
-
<?php $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' ); ?>
|
88 |
-
<table class="form-table">
|
89 |
-
<tr valign="top">
|
90 |
-
<th><?php _e( 'Purge All Cache', 'nginx-helper' ); ?></th>
|
91 |
-
<td>
|
92 |
-
<a href="<?php echo $nonced_url; ?>" class="button-primary"><?php _e( 'Purge Cache', 'nginx-helper' ); ?></a>
|
93 |
-
</td>
|
94 |
-
</tr>
|
95 |
-
</table>
|
96 |
-
</div>
|
97 |
-
</form>
|
98 |
-
</div> <!-- End of .postbox -->
|
99 |
-
<form id="post_form" method="post" action="#" name="smart_http_expire_form" class="clearfix">
|
100 |
-
<div class="postbox">
|
101 |
-
<h3 class="hndle">
|
102 |
-
<span><?php _e( 'Plugin Options', 'nginx-helper' ); ?></span>
|
103 |
-
</h3>
|
104 |
-
<?php if ( !(!is_network_admin() && is_multisite() ) ) { ?>
|
105 |
-
<div class="inside">
|
106 |
-
<input type="hidden" name="is_submit" value="1" />
|
107 |
-
<table class="form-table">
|
108 |
-
<tr valign="top">
|
109 |
-
<td>
|
110 |
-
<input type="checkbox" value="1" id="enable_purge" name="enable_purge" <?php checked( $rt_wp_nginx_helper->options['enable_purge'], 1 ); ?> />
|
111 |
-
<label for="enable_purge">
|
112 |
-
<?php printf( __( 'Enable Cache Purge (<a target="_blank" href="%s" title="External settings for nginx">requires external settings for nginx</a>)', 'nginx-helper' ), $nginx_setting_link ); ?>
|
113 |
-
</label>
|
114 |
-
</td>
|
115 |
-
</tr>
|
116 |
-
<?php if ( is_network_admin() ) { ?>
|
117 |
-
<tr valign="top">
|
118 |
-
<td>
|
119 |
-
<input type="checkbox" value="1" id="enable_map" name="enable_map"<?php checked( $rt_wp_nginx_helper->options['enable_map'], 1 ); ?> />
|
120 |
-
<label for="enable_map"><?php _e( 'Enable Nginx Map.', 'nginx-helper' ); ?></label>
|
121 |
-
</td>
|
122 |
-
</tr>
|
123 |
-
<?php } ?>
|
124 |
-
<tr valign="top">
|
125 |
-
<td>
|
126 |
-
<input type="checkbox" value="1" id="enable_log" name="enable_log"<?php checked( $rt_wp_nginx_helper->options['enable_log'], 1 ); ?> />
|
127 |
-
<label for="enable_log"><?php _e( 'Enable Logging', 'nginx-helper' ); ?></label>
|
128 |
-
</td>
|
129 |
-
</tr>
|
130 |
-
<tr valign="top">
|
131 |
-
<td>
|
132 |
-
<input type="checkbox" value="1" id="enable_stamp" name="enable_stamp"<?php checked( $rt_wp_nginx_helper->options['enable_stamp'], 1 ); ?> />
|
133 |
-
<label for="enable_stamp"><?php _e( 'Enable Nginx Timestamp in HTML', 'nginx-helper' ); ?></label>
|
134 |
-
</td>
|
135 |
-
</tr>
|
136 |
-
</table>
|
137 |
-
</div> <!-- End of .inside -->
|
138 |
-
</div>
|
139 |
-
|
140 |
-
<div class="postbox enable_purge"<?php echo ( $rt_wp_nginx_helper->options['enable_purge'] == false ) ? ' style="display: none;"' : ''; ?>>
|
141 |
-
<h3 class="hndle">
|
142 |
-
<span><?php _e( 'Purging Options', 'nginx-helper' ); ?></span>
|
143 |
-
</h3>
|
144 |
-
<div class="inside">
|
145 |
-
|
146 |
-
<table class="form-table rtnginx-table">
|
147 |
-
<tr valign="top">
|
148 |
-
<th scope="row"><h4><?php _e( 'Purge Homepage:', 'nginx-helper' ); ?></h4></th>
|
149 |
-
<td>
|
150 |
-
<fieldset>
|
151 |
-
<legend class="screen-reader-text">
|
152 |
-
<span> <?php _e( 'when a post/page/custom post is modified or added.', 'nginx-helper' ); ?></span>
|
153 |
-
</legend>
|
154 |
-
<label for="purge_homepage_on_edit">
|
155 |
-
<input type="checkbox" value="1" id="purge_homepage_on_edit" name="purge_homepage_on_edit"<?php checked( $rt_wp_nginx_helper->options['purge_homepage_on_edit'], 1 ); ?> />
|
156 |
-
<?php _e( 'when a <strong>post</strong> (or page/custom post) is <strong>modified</strong> or <strong>added</strong>.', 'nginx-helper' ); ?>
|
157 |
-
</label><br />
|
158 |
-
</fieldset>
|
159 |
-
<fieldset>
|
160 |
-
<legend class="screen-reader-text">
|
161 |
-
<span> <?php _e( 'when an existing post/page/custom post is modified.', 'nginx-helper' ); ?></span>
|
162 |
-
</legend>
|
163 |
-
<label for="purge_homepage_on_del">
|
164 |
-
<input type="checkbox" value="1" id="purge_homepage_on_del" name="purge_homepage_on_del"<?php checked( $rt_wp_nginx_helper->options['purge_homepage_on_del'], 1 ); ?> />
|
165 |
-
<?php _e( 'when a <strong>published post</strong> (or page/custom post) is <strong>trashed</strong>.', 'nginx-helper' ); ?></label><br />
|
166 |
-
</fieldset>
|
167 |
-
</td>
|
168 |
-
</tr>
|
169 |
-
</table>
|
170 |
-
<table class="form-table rtnginx-table">
|
171 |
-
<tr valign="top">
|
172 |
-
<th scope="row">
|
173 |
-
<h4><?php _e( 'Purge Post/Page/Custom Post Type:', 'nginx-helper' ); ?></h4>
|
174 |
-
</th>
|
175 |
-
<td>
|
176 |
-
<fieldset>
|
177 |
-
<legend class="screen-reader-text">
|
178 |
-
<span> <?php _e( 'when a post/page/custom post is published.', 'nginx-helper' ); ?></span>
|
179 |
-
</legend>
|
180 |
-
<label for="purge_page_on_mod">
|
181 |
-
<input type="checkbox" value="1" id="purge_page_on_mod" name="purge_page_on_mod"<?php checked( $rt_wp_nginx_helper->options['purge_page_on_mod'], 1 ); ?>>
|
182 |
-
<?php _e( 'when a <strong>post</strong> is <strong>published</strong>.', 'nginx-helper' ); ?>
|
183 |
-
</label><br />
|
184 |
-
</fieldset>
|
185 |
-
<fieldset>
|
186 |
-
<legend class="screen-reader-text">
|
187 |
-
<span> <?php _e( 'when a comment is approved/published.', 'nginx-helper' ); ?></span>
|
188 |
-
</legend>
|
189 |
-
<label for="purge_page_on_new_comment">
|
190 |
-
<input type="checkbox" value="1" id="purge_page_on_new_comment" name="purge_page_on_new_comment"<?php checked( $rt_wp_nginx_helper->options['purge_page_on_new_comment'], 1 ); ?>>
|
191 |
-
<?php _e( 'when a <strong>comment</strong> is <strong>approved/published</strong>.', 'nginx-helper' ); ?>
|
192 |
-
</label><br />
|
193 |
-
</fieldset>
|
194 |
-
<fieldset>
|
195 |
-
<legend class="screen-reader-text">
|
196 |
-
<span> <?php _e( 'when a comment is unapproved/deleted.', 'nginx-helper' ); ?></span>
|
197 |
-
</legend>
|
198 |
-
<label for="purge_page_on_deleted_comment">
|
199 |
-
<input type="checkbox" value="1" id="purge_page_on_deleted_comment" name="purge_page_on_deleted_comment"<?php checked( $rt_wp_nginx_helper->options['purge_page_on_deleted_comment'], 1 ); ?>>
|
200 |
-
<?php _e( 'when a <strong>comment</strong> is <strong>unapproved/deleted</strong>.', 'nginx-helper' ); ?>
|
201 |
-
</label><br />
|
202 |
-
</fieldset>
|
203 |
-
</td>
|
204 |
-
</tr>
|
205 |
-
</table>
|
206 |
-
<table class="form-table rtnginx-table">
|
207 |
-
<tr valign="top">
|
208 |
-
<th scope="row">
|
209 |
-
<h4><?php _e( 'Purge Archives:', 'nginx-helper' ); ?></h4>
|
210 |
-
<small><?php _e( '(date, category, tag, author, custom taxonomies)', 'nginx-helper' ); ?></small>
|
211 |
-
</th>
|
212 |
-
<td>
|
213 |
-
<fieldset>
|
214 |
-
<legend class="screen-reader-text">
|
215 |
-
<span> <?php _e( 'when an post/page/custom post is modified or added.</span>', 'nginx-helper' ); ?>
|
216 |
-
</legend>
|
217 |
-
<label for="purge_archive_on_edit">
|
218 |
-
<input type="checkbox" value="1" id="purge_archive_on_edit" name="purge_archive_on_edit"<?php checked( $rt_wp_nginx_helper->options['purge_archive_on_edit'], 1 ); ?> />
|
219 |
-
<?php _e( 'when a <strong>post</strong> (or page/custom post) is <strong>modified</strong> or <strong>added</strong>.', 'nginx-helper' ); ?>
|
220 |
-
</label><br />
|
221 |
-
</fieldset>
|
222 |
-
<fieldset>
|
223 |
-
<legend class="screen-reader-text">
|
224 |
-
<span> <?php _e( 'when an existing post/page/custom post is trashed.</span>', 'nginx-helper' ); ?>
|
225 |
-
</legend>
|
226 |
-
<label for="purge_archive_on_del">
|
227 |
-
<input type="checkbox" value="1" id="purge_archive_on_del" name="purge_archive_on_del"<?php checked( $rt_wp_nginx_helper->options['purge_archive_on_del'], 1 ); ?> />
|
228 |
-
<?php _e( 'when a <strong>published post</strong> (or page/custom post) is <strong>trashed</strong>.', 'nginx-helper' ); ?>
|
229 |
-
</label><br />
|
230 |
-
</fieldset>
|
231 |
-
<br />
|
232 |
-
<fieldset>
|
233 |
-
<legend class="screen-reader-text">
|
234 |
-
<span> <?php _e( 'when a comment is approved/published.</span>', 'nginx-helper' ); ?>
|
235 |
-
</legend>
|
236 |
-
<label for="purge_archive_on_new_comment">
|
237 |
-
<input type="checkbox" value="1" id="purge_archive_on_new_comment" name="purge_archive_on_new_comment"<?php checked( $rt_wp_nginx_helper->options['purge_archive_on_new_comment'], 1 ); ?> />
|
238 |
-
<?php _e( 'when a <strong>comment</strong> is <strong>approved/published</strong>.', 'nginx-helper' ); ?>
|
239 |
-
</label><br />
|
240 |
-
</fieldset>
|
241 |
-
<fieldset>
|
242 |
-
<legend class="screen-reader-text">
|
243 |
-
<span> <?php _e( 'when a comment is unapproved/deleted.</span>', 'nginx-helper' ); ?>
|
244 |
-
</legend>
|
245 |
-
<label for="purge_archive_on_deleted_comment">
|
246 |
-
<input type="checkbox" value="1" id="purge_archive_on_deleted_comment" name="purge_archive_on_deleted_comment"<?php checked( $rt_wp_nginx_helper->options['purge_archive_on_deleted_comment'], 1 ); ?> />
|
247 |
-
<?php _e( 'when a <strong>comment</strong> is <strong>unapproved/deleted</strong>.', 'nginx-helper' ); ?>
|
248 |
-
</label><br />
|
249 |
-
</fieldset>
|
250 |
-
|
251 |
-
</td>
|
252 |
-
</tr>
|
253 |
-
</table>
|
254 |
-
<table class="form-table rtnginx-table">
|
255 |
-
<tr valign="top">
|
256 |
-
<th scope="row">
|
257 |
-
<h4><?php _e( 'Purge Method:', 'nginx-helper' ); ?></h4>
|
258 |
-
</th>
|
259 |
-
<td>
|
260 |
-
<fieldset>
|
261 |
-
<legend class="screen-reader-text">
|
262 |
-
<span> <?php _e( 'when a post/page/custom post is published.', 'nginx-helper' ); ?></span>
|
263 |
-
</legend>
|
264 |
-
<label for="purge_method_get_request">
|
265 |
-
<input type="radio" value="get_request" id="purge_method_get_request" name="purge_method"<?php checked( isset( $rt_wp_nginx_helper->options['purge_method'] ) ? $rt_wp_nginx_helper->options['purge_method'] : 'get_request', 'get_request' ); ?>>
|
266 |
-
<?php _e( 'Using a GET request to <strong>PURGE/url</strong> (Default option)', 'nginx-helper' ); ?><br />
|
267 |
-
<small><?php _e( 'Uses the <strong><a href="https://github.com/FRiCKLE/ngx_cache_purge">ngx_cache_purge</a></strong> module. ', 'nginx-helper' ); ?></small>
|
268 |
-
</label><br />
|
269 |
-
<label for="purge_method_unlink_files">
|
270 |
-
<input type="radio" value="unlink_files" id="purge_method_unlink_files" name="purge_method"<?php checked( isset( $rt_wp_nginx_helper->options['purge_method'] ) ? $rt_wp_nginx_helper->options['purge_method'] : '', 'unlink_files' ); ?>>
|
271 |
-
<?php _e( 'Delete local server cache files', 'nginx-helper' ); ?><br />
|
272 |
-
<small><?php _e( 'Checks for matching cache file in <strong>RT_WP_NGINX_HELPER_CACHE_PATH</strong>. Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri"). ', 'nginx-helper' ); ?></small>
|
273 |
-
|
274 |
-
</label><br />
|
275 |
-
</fieldset>
|
276 |
-
</td>
|
277 |
-
</tr>
|
278 |
-
</table>
|
279 |
-
</div> <!-- End of .inside -->
|
280 |
-
</div><?php
|
281 |
-
} // End of if ( !( !is_network_admin() && is_multisite() ) )
|
282 |
-
|
283 |
-
|
284 |
-
if ( is_network_admin() ) {
|
285 |
-
?>
|
286 |
-
<div class="postbox enable_map"<?php echo ( $rt_wp_nginx_helper->options['enable_map'] == false ) ? ' style="display: none;"' : ''; ?>>
|
287 |
-
<h3 class="hndle">
|
288 |
-
<span><?php _e( 'Nginx Map', 'nginx-helper' ); ?></span>
|
289 |
-
</h3>
|
290 |
-
<div class="inside"><?php if ( !is_writable( $rt_wp_nginx_helper->functional_asset_path() . 'map.conf' ) ) { ?>
|
291 |
-
<span class="error fade" style="display: block"><p><?php printf( __( 'Can\'t write on map file.<br /><br />Check you have write permission on <strong>%s</strong>', 'nginx-helper' ), $rt_wp_nginx_helper->functional_asset_path() . 'map.conf' ); ?></p></span><?php }
|
292 |
-
?>
|
293 |
-
|
294 |
-
<table class="form-table rtnginx-table">
|
295 |
-
<tr>
|
296 |
-
<th><?php _e( 'Nginx Map path to include in nginx settings<br /><small>(recommended)</small>', 'nginx-helper' ); ?></th>
|
297 |
-
<td>
|
298 |
-
<pre><?php echo $rt_wp_nginx_helper->functional_asset_path() . 'map.conf'; ?></pre>
|
299 |
-
</td>
|
300 |
-
</tr>
|
301 |
-
<tr>
|
302 |
-
<th><?php _e( 'Or,<br />Text to manually copy and paste in nginx settings<br /><small>(if your network is small and new sites are not added frequently)</small>', 'nginx-helper' ); ?></th>
|
303 |
-
<td>
|
304 |
-
<pre id="map"><?php echo $rt_wp_nginx_helper->get_map() ?></pre>
|
305 |
-
</td>
|
306 |
-
</tr>
|
307 |
-
</table>
|
308 |
-
</div> <!-- End of .inside -->
|
309 |
-
</div>
|
310 |
-
<?php } ?>
|
311 |
-
|
312 |
-
<div class="postbox enable_log"<?php echo ( $rt_wp_nginx_helper->options['enable_log'] == false ) ? ' style="display: none;"' : ''; ?>>
|
313 |
-
<h3 class="hndle">
|
314 |
-
<span><?php _e( 'Logging Options', 'nginx-helper' ); ?></span>
|
315 |
-
</h3>
|
316 |
-
<div class="inside">
|
317 |
-
<?php
|
318 |
-
$path = $rt_wp_nginx_helper->functional_asset_path();
|
319 |
-
if ( !is_dir( $path ) ) {
|
320 |
-
mkdir( $path );
|
321 |
-
}
|
322 |
-
if ( !file_exists( $path . 'nginx.log' ) ) {
|
323 |
-
$log = fopen( $path . 'nginx.log', 'w' );
|
324 |
-
fclose( $log );
|
325 |
-
}
|
326 |
-
if ( is_writable( $path . 'nginx.log' ) ) {
|
327 |
-
$rt_wp_nginx_purger->log( "+++++++++" );
|
328 |
-
$rt_wp_nginx_purger->log( "+Log Test" );
|
329 |
-
$rt_wp_nginx_purger->log( "+++++++++" );
|
330 |
-
}
|
331 |
-
if ( !is_writable( $path . 'nginx.log' ) ) {
|
332 |
-
?>
|
333 |
-
<span class="error fade" style="display : block"><p><?php printf( __( 'Can\'t write on log file.<br /><br />Check you have write permission on <strong>%s</strong>', 'nginx-helper' ), $rt_wp_nginx_helper->functional_asset_path() . 'nginx.log' ); ?></p></span><?php }
|
334 |
-
?>
|
335 |
-
|
336 |
-
<table class="form-table rtnginx-table">
|
337 |
-
<tbody>
|
338 |
-
<tr>
|
339 |
-
<th><label for="rt_wp_nginx_helper_logs_path"><?php _e( 'Logs path', 'nginx-helper' ); ?></label></th>
|
340 |
-
<td><pre><?php echo $rt_wp_nginx_helper->functional_asset_path(); ?>nginx.log</pre></td>
|
341 |
-
</tr>
|
342 |
-
<tr>
|
343 |
-
<th><label for="rt_wp_nginx_helper_logs_link"><?php _e( 'View Log', 'nginx-helper' ); ?></label></th>
|
344 |
-
<td><a target="_blank" href="<?php echo $rt_wp_nginx_helper->functional_asset_url(); ?>nginx.log"><?php _e( 'Log', 'nginx-helper' ); ?></a></td>
|
345 |
-
</tr>
|
346 |
-
<tr>
|
347 |
-
<th><label for="rt_wp_nginx_helper_log_level"><?php _e( 'Log level', 'nginx-helper' ); ?></label></th>
|
348 |
-
<td>
|
349 |
-
<select name="log_level">
|
350 |
-
<option value="NONE"<?php selected( $rt_wp_nginx_helper->options['log_level'], 'NONE' ); ?>><?php _e( 'None', 'nginx-helper' ); ?></option>
|
351 |
-
<option value="INFO"<?php selected( $rt_wp_nginx_helper->options['log_level'], 'INFO' ); ?>><?php _e( 'Info', 'nginx-helper' ); ?></option>
|
352 |
-
<option value="WARNING"<?php selected( $rt_wp_nginx_helper->options['log_level'], 'WARNING' ); ?>><?php _e( 'Warning', 'nginx-helper' ); ?></option>
|
353 |
-
<option value="ERROR"<?php selected( $rt_wp_nginx_helper->options['log_level'], 'ERROR' ); ?>><?php _e( 'Error', 'nginx-helper' ); ?></option>
|
354 |
-
</select>
|
355 |
-
</td>
|
356 |
-
</tr>
|
357 |
-
<tr>
|
358 |
-
<th><label for="log_filesize"><?php _e( 'Max log file size', 'nginx-helper' ); ?></label></th>
|
359 |
-
<td>
|
360 |
-
<input id="log_filesize" class="small-text" type="text" name="log_filesize" value="<?php echo $rt_wp_nginx_helper->options['log_filesize'] ?>" /> <?php _e( 'Mb', 'nginx-helper' );
|
361 |
-
if ( $error_log_filesize ) {
|
362 |
-
?>
|
363 |
-
<p class="error fade" style="display: block;"><?php echo $error_log_filesize; ?></p><?php }
|
364 |
-
?>
|
365 |
-
</td>
|
366 |
-
</tr>
|
367 |
-
</tbody>
|
368 |
-
</table>
|
369 |
-
</div> <!-- End of .inside -->
|
370 |
-
</div><?php submit_button( __( 'Save All Changes', 'nginx-helper' ), 'primary large', 'smart_http_expire_save', true ); ?>
|
371 |
-
</form><!-- End of #post_form --><?php
|
372 |
-
}
|
373 |
-
|
374 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/lib/nginx-sidebar.php
DELETED
@@ -1,58 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace rtCamp\WP\Nginx {
|
4 |
-
|
5 |
-
function default_admin_sidebar() { ?>
|
6 |
-
<div class="postbox" id="support">
|
7 |
-
<h3 class="hndle">
|
8 |
-
<span><?php _e( 'Need Help?', 'nginx-helper' ); ?></span>
|
9 |
-
</h3>
|
10 |
-
<div class="inside">
|
11 |
-
<p><?php printf( __( 'Please use our <a href="%s">free support forum</a>.', 'nginx-helper' ), 'http://rtcamp.com/support/forum/wordpress-nginx/' ); ?></p>
|
12 |
-
</div>
|
13 |
-
</div>
|
14 |
-
|
15 |
-
<div class="postbox" id="social">
|
16 |
-
<h3 class="hndle">
|
17 |
-
<span><?php _e( 'Getting Social is Good', 'nginx-helper' ); ?></span>
|
18 |
-
</h3>
|
19 |
-
<div style="text-align:center;" class="inside">
|
20 |
-
<a class="nginx-helper-facebook" title="<?php _e( 'Become a fan on Facebook', 'nginx-helper' ); ?>" target="_blank" href="http://www.facebook.com/rtCamp.solutions/"></a>
|
21 |
-
<a class="nginx-helper-twitter" title="<?php _e( 'Follow us on Twitter', 'nginx-helper' ); ?>" target="_blank" href="https://twitter.com/rtcamp/"></a>
|
22 |
-
<a class="nginx-helper-gplus" title="<?php _e( 'Add to Circle', 'nginx-helper' ); ?>" target="_blank" href="https://plus.google.com/110214156830549460974/posts"></a>
|
23 |
-
<a class="nginx-helper-rss" title="<?php _e( 'Subscribe to our feeds', 'nginx-helper' ); ?>" target="_blank" href="http://feeds.feedburner.com/rtcamp/"></a>
|
24 |
-
</div>
|
25 |
-
</div>
|
26 |
-
|
27 |
-
<div class="postbox" id="useful-links">
|
28 |
-
<h3 class="hndle">
|
29 |
-
<span><?php _e( 'Useful Links', 'nginx-helper' ); ?></span>
|
30 |
-
</h3>
|
31 |
-
<div class="inside">
|
32 |
-
<ul role="list">
|
33 |
-
<li role="listitem">
|
34 |
-
<a href="https://rtcamp.com/wordpress-nginx/" title="<?php _e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?>"><?php _e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?></a>
|
35 |
-
</li>
|
36 |
-
<li role="listitem">
|
37 |
-
<a href="https://rtcamp.com/services/wordPress-themes-design-development/" title="<?php _e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?>"><?php _e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?></a>
|
38 |
-
</li>
|
39 |
-
<li role="listitem">
|
40 |
-
<a href="http://rtcamp.com/services/wordpress-plugins/" title="<?php _e( 'WordPress Plugin Development', 'nginx-helper' ); ?>"><?php _e( 'WordPress Plugin Development', 'nginx-helper' ); ?></a>
|
41 |
-
</li>
|
42 |
-
<li role="listitem">
|
43 |
-
<a href="http://rtcamp.com/services/custom-wordpress-solutions/" title="<?php _e( 'WordPress Consultancy', 'nginx-helper' ); ?>"><?php _e( 'WordPress Consultancy', 'nginx-helper' ); ?></a>
|
44 |
-
</li>
|
45 |
-
<li role="listitem">
|
46 |
-
<a href="https://rtcamp.com/easyengine/" title="<?php _e( 'easyengine (ee)', 'nginx-helper' ); ?>"><?php _e( 'easyengine (ee)', 'nginx-helper' ); ?></a>
|
47 |
-
</li>
|
48 |
-
</ul>
|
49 |
-
</div>
|
50 |
-
</div>
|
51 |
-
|
52 |
-
<div class="postbox" id="latest_news">
|
53 |
-
<div title="<?php _e( 'Click to toggle', 'nginx-helper' ); ?>" class="handlediv"><br /></div>
|
54 |
-
<h3 class="hndle"><span><?php _e( 'Latest News', 'nginx-helper' ); ?></span></h3>
|
55 |
-
<div class="inside"><img src ="<?php echo admin_url(); ?>/images/wpspin_light.gif" /><?php _e( 'Loading...', 'nginx-helper' ); ?></div>
|
56 |
-
</div><?php
|
57 |
-
} // End of default_admin_sidebar()
|
58 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/admin/lib/nginx-support.php
DELETED
@@ -1,29 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace rtCamp\WP\Nginx {
|
4 |
-
function nginx_support_options_page() { ?>
|
5 |
-
<form id="support" action="" method="post" class="clearfix">
|
6 |
-
<div class="postbox">
|
7 |
-
<h3 class="hndle">
|
8 |
-
<span><?php _e( 'Support Forums', 'nginx-helper' ); ?></span>
|
9 |
-
</h3>
|
10 |
-
<div class="inside">
|
11 |
-
<table class="form-table">
|
12 |
-
<tr valign="top">
|
13 |
-
<th><?php _e( 'Free Support', 'nginx-helper' ); ?></th>
|
14 |
-
<td>
|
15 |
-
<a href="https://rtcamp.com/support/forum/wordpress-nginx/" title="<?php _e( 'Free Support Forum', 'nginx-helper' ); ?>" target="_blank"><?php _e( 'Link to forum', 'nginx-helper' ); ?></a>
|
16 |
-
</td>
|
17 |
-
</tr>
|
18 |
-
<tr valign="top">
|
19 |
-
<th><?php _e( 'Premium Support', 'nginx-helper' ); ?></th>
|
20 |
-
<td>
|
21 |
-
<a href="https://rtcamp.com/wordpress-nginx/pricing/" title="<?php _e( 'Premium Support Forum', 'nginx-helper' ); ?>" target="_blank"><?php _e( 'Link to forum', 'nginx-helper' ); ?></a>
|
22 |
-
</td>
|
23 |
-
</tr>
|
24 |
-
</table>
|
25 |
-
</div>
|
26 |
-
</div>
|
27 |
-
</form><?php
|
28 |
-
}
|
29 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/compatibility.php
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
|
3 |
-
namespace rtCamp\WP\Nginx {
|
4 |
-
|
5 |
-
class Compatibility {
|
6 |
-
|
7 |
-
protected $have_nginx;
|
8 |
-
|
9 |
-
public static function instance() {
|
10 |
-
static $self = false;
|
11 |
-
if (!$self) {
|
12 |
-
$self = new Compatibility();
|
13 |
-
}
|
14 |
-
return $self;
|
15 |
-
}
|
16 |
-
|
17 |
-
private function __construct() {
|
18 |
-
$this->have_nginx = ('nginx' == substr($_SERVER['SERVER_SOFTWARE'], 0, 5));
|
19 |
-
if ($this->have_nginx) {
|
20 |
-
add_filter('got_rewrite', array($this, 'got_rewrite'), 999);
|
21 |
-
|
22 |
-
// For compatibility with several plugins and nginx HTTPS proxying schemes
|
23 |
-
if (empty($_SERVER['HTTPS']) || 'off' == $_SERVER['HTTPS']) {
|
24 |
-
unset($_SERVER['HTTPS']);
|
25 |
-
}
|
26 |
-
}
|
27 |
-
}
|
28 |
-
|
29 |
-
public function got_rewrite($got) {
|
30 |
-
return true;
|
31 |
-
}
|
32 |
-
|
33 |
-
public function haveNginx() {
|
34 |
-
return $this->have_nginx;
|
35 |
-
}
|
36 |
-
}
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/languages/nginx-helper.mo
DELETED
Binary file
|
trunk/languages/nginx-helper.po
DELETED
@@ -1,501 +0,0 @@
|
|
1 |
-
msgid ""
|
2 |
-
msgstr ""
|
3 |
-
"Project-Id-Version: Nginx Helper 1.7.6\n"
|
4 |
-
"Report-Msgid-Bugs-To: \n"
|
5 |
-
"POT-Creation-Date: 2014-01-02 17:41+0530\n"
|
6 |
-
"PO-Revision-Date: 2014-01-02 17:41+0530\n"
|
7 |
-
"Last-Translator: rtCamp <support@rtcamp.com>\n"
|
8 |
-
"Language-Team: rtCampers <support@rtcamp.com>\n"
|
9 |
-
"Language: en\n"
|
10 |
-
"MIME-Version: 1.0\n"
|
11 |
-
"Content-Type: text/plain; charset=UTF-8\n"
|
12 |
-
"Content-Transfer-Encoding: 8bit\n"
|
13 |
-
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c\n"
|
14 |
-
"X-Poedit-Basepath: .\n"
|
15 |
-
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
16 |
-
"X-Generator: Poedit 1.5.4\n"
|
17 |
-
"X-Poedit-SearchPath-0: ..\n"
|
18 |
-
|
19 |
-
#: ../nginx-helper.php:254
|
20 |
-
msgid "Purge initiated"
|
21 |
-
msgstr ""
|
22 |
-
|
23 |
-
#: ../nginx-helper.php:340
|
24 |
-
msgid "Settings"
|
25 |
-
msgstr ""
|
26 |
-
|
27 |
-
#: ../nginx-helper.php:367
|
28 |
-
msgid "No items"
|
29 |
-
msgstr ""
|
30 |
-
|
31 |
-
#: ../nginx-helper.php:372
|
32 |
-
msgid "Posted "
|
33 |
-
msgstr ""
|
34 |
-
|
35 |
-
#: ../purger.php:366
|
36 |
-
#, php-format
|
37 |
-
msgid "Purging homepage '%s'"
|
38 |
-
msgstr ""
|
39 |
-
|
40 |
-
#: ../purger.php:376
|
41 |
-
msgid "Purging personal urls"
|
42 |
-
msgstr ""
|
43 |
-
|
44 |
-
#: ../purger.php:384
|
45 |
-
msgid "No personal urls available"
|
46 |
-
msgstr ""
|
47 |
-
|
48 |
-
#: ../purger.php:392
|
49 |
-
msgid "Purging category archives"
|
50 |
-
msgstr ""
|
51 |
-
|
52 |
-
#: ../purger.php:396
|
53 |
-
#, php-format
|
54 |
-
msgid "Purging category '%d'"
|
55 |
-
msgstr ""
|
56 |
-
|
57 |
-
#: ../purger.php:406
|
58 |
-
msgid "Purging tags archives"
|
59 |
-
msgstr ""
|
60 |
-
|
61 |
-
#: ../purger.php:410 ../purger.php:468
|
62 |
-
#, php-format
|
63 |
-
msgid "Purging tag '%s' (id %d)"
|
64 |
-
msgstr ""
|
65 |
-
|
66 |
-
#: ../purger.php:420
|
67 |
-
msgid "Purging post custom taxonomies related"
|
68 |
-
msgstr ""
|
69 |
-
|
70 |
-
#: ../purger.php:424 ../purger.php:485
|
71 |
-
#, php-format
|
72 |
-
msgid "Purging custom taxonomy '%s'"
|
73 |
-
msgstr ""
|
74 |
-
|
75 |
-
#: ../purger.php:434 ../purger.php:495
|
76 |
-
#, php-format
|
77 |
-
msgid "Your built-in taxonomy '%s' has param '_builtin' set to false."
|
78 |
-
msgstr ""
|
79 |
-
|
80 |
-
#: ../purger.php:438 ../purger.php:499
|
81 |
-
msgid "No custom taxonomies"
|
82 |
-
msgstr ""
|
83 |
-
|
84 |
-
#: ../purger.php:446
|
85 |
-
msgid "Purging all categories"
|
86 |
-
msgstr ""
|
87 |
-
|
88 |
-
#: ../purger.php:451
|
89 |
-
#, php-format
|
90 |
-
msgid "Purging category '%s' (id %d)"
|
91 |
-
msgstr ""
|
92 |
-
|
93 |
-
#: ../purger.php:455
|
94 |
-
msgid "No categories archives"
|
95 |
-
msgstr ""
|
96 |
-
|
97 |
-
#: ../purger.php:463
|
98 |
-
msgid "Purging all tags"
|
99 |
-
msgstr ""
|
100 |
-
|
101 |
-
#: ../purger.php:472
|
102 |
-
msgid "No tags archives"
|
103 |
-
msgstr ""
|
104 |
-
|
105 |
-
#: ../purger.php:480
|
106 |
-
msgid "Purging all custom taxonomies"
|
107 |
-
msgstr ""
|
108 |
-
|
109 |
-
#: ../purger.php:516
|
110 |
-
msgid "Purging all posts, pages and custom post types."
|
111 |
-
msgstr ""
|
112 |
-
|
113 |
-
#: ../purger.php:526
|
114 |
-
#, php-format
|
115 |
-
msgid "Purging post id '%d' (post type '%s')"
|
116 |
-
msgstr ""
|
117 |
-
|
118 |
-
#: ../purger.php:530
|
119 |
-
msgid "No posts"
|
120 |
-
msgstr ""
|
121 |
-
|
122 |
-
#: ../purger.php:538
|
123 |
-
msgid "Purging all date-based archives."
|
124 |
-
msgstr ""
|
125 |
-
|
126 |
-
#: ../purger.php:553
|
127 |
-
msgid "Purging all daily archives."
|
128 |
-
msgstr ""
|
129 |
-
|
130 |
-
#: ../purger.php:566
|
131 |
-
#, php-format
|
132 |
-
msgid "Purging daily archive '%s/%s/%s'"
|
133 |
-
msgstr ""
|
134 |
-
|
135 |
-
#: ../purger.php:570
|
136 |
-
msgid "No daily archives"
|
137 |
-
msgstr ""
|
138 |
-
|
139 |
-
#: ../purger.php:578
|
140 |
-
msgid "Purging all monthly archives."
|
141 |
-
msgstr ""
|
142 |
-
|
143 |
-
#: ../purger.php:591
|
144 |
-
#, php-format
|
145 |
-
msgid "Purging monthly archive '%s/%s'"
|
146 |
-
msgstr ""
|
147 |
-
|
148 |
-
#: ../purger.php:595
|
149 |
-
msgid "No monthly archives"
|
150 |
-
msgstr ""
|
151 |
-
|
152 |
-
#: ../purger.php:603
|
153 |
-
msgid "Purging all yearly archives."
|
154 |
-
msgstr ""
|
155 |
-
|
156 |
-
#: ../purger.php:616
|
157 |
-
#, php-format
|
158 |
-
msgid "Purging yearly archive '%s'"
|
159 |
-
msgstr ""
|
160 |
-
|
161 |
-
#: ../purger.php:620
|
162 |
-
msgid "No yearly archives"
|
163 |
-
msgstr ""
|
164 |
-
|
165 |
-
#: ../purger.php:626
|
166 |
-
msgid "Let's purge everything!"
|
167 |
-
msgstr ""
|
168 |
-
|
169 |
-
#: ../purger.php:638
|
170 |
-
msgid "Everthing purged!"
|
171 |
-
msgstr ""
|
172 |
-
|
173 |
-
#: ../purger.php:645
|
174 |
-
msgid "Term taxonomy edited or deleted"
|
175 |
-
msgstr ""
|
176 |
-
|
177 |
-
#: ../purger.php:648
|
178 |
-
#, php-format
|
179 |
-
msgid "Term taxonomy '%s' edited, (tt_id '%d', term_id '%d', taxonomy '%s')"
|
180 |
-
msgstr ""
|
181 |
-
|
182 |
-
#: ../purger.php:650
|
183 |
-
#, php-format
|
184 |
-
msgid ""
|
185 |
-
"A term taxonomy has been deleted from taxonomy '%s', (tt_id '%d', term_id "
|
186 |
-
"'%d')"
|
187 |
-
msgstr ""
|
188 |
-
|
189 |
-
#: ../purger.php:663
|
190 |
-
msgid "Widget saved, moved or removed in a sidebar"
|
191 |
-
msgstr ""
|
192 |
-
|
193 |
-
#: ../admin/admin.php:26
|
194 |
-
msgid "General"
|
195 |
-
msgstr ""
|
196 |
-
|
197 |
-
#: ../admin/admin.php:30
|
198 |
-
msgid "Support"
|
199 |
-
msgstr ""
|
200 |
-
|
201 |
-
#: ../admin/admin.php:40 ../admin/admin.php:47
|
202 |
-
msgid "Nginx Helper"
|
203 |
-
msgstr ""
|
204 |
-
|
205 |
-
#: ../admin/admin.php:78
|
206 |
-
msgid "Nginx Settings"
|
207 |
-
msgstr ""
|
208 |
-
|
209 |
-
#: ../admin/admin.php:123 ../admin/lib/nginx-general.php:79
|
210 |
-
#: ../admin/lib/nginx-general.php:89
|
211 |
-
msgid "Purge Cache"
|
212 |
-
msgstr ""
|
213 |
-
|
214 |
-
#: ../admin/install.php:22
|
215 |
-
msgid "Sorry, you need to be an administrator to use Nginx Helper"
|
216 |
-
msgstr ""
|
217 |
-
|
218 |
-
#: ../admin/lib/nginx-general.php:26
|
219 |
-
msgid "Log file size must be a number"
|
220 |
-
msgstr ""
|
221 |
-
|
222 |
-
#: ../admin/lib/nginx-general.php:61
|
223 |
-
msgid "Settings saved."
|
224 |
-
msgstr ""
|
225 |
-
|
226 |
-
#: ../admin/lib/nginx-general.php:87
|
227 |
-
msgid "Purge All Cache"
|
228 |
-
msgstr ""
|
229 |
-
|
230 |
-
#: ../admin/lib/nginx-general.php:99
|
231 |
-
msgid "Plugin Options"
|
232 |
-
msgstr ""
|
233 |
-
|
234 |
-
#: ../admin/lib/nginx-general.php:109
|
235 |
-
#, php-format
|
236 |
-
msgid ""
|
237 |
-
"Enable Cache Purge (<a target=\"_blank\" href=\"%s\" title=\"External "
|
238 |
-
"settings for nginx\">requires external settings for nginx</a>)"
|
239 |
-
msgstr ""
|
240 |
-
|
241 |
-
#: ../admin/lib/nginx-general.php:117
|
242 |
-
msgid "Enable Nginx Map."
|
243 |
-
msgstr ""
|
244 |
-
|
245 |
-
#: ../admin/lib/nginx-general.php:124
|
246 |
-
msgid "Enable Logging"
|
247 |
-
msgstr ""
|
248 |
-
|
249 |
-
#: ../admin/lib/nginx-general.php:130
|
250 |
-
msgid "Enable Nginx Timestamp in HTML"
|
251 |
-
msgstr ""
|
252 |
-
|
253 |
-
#: ../admin/lib/nginx-general.php:139
|
254 |
-
msgid "Purging Options"
|
255 |
-
msgstr ""
|
256 |
-
|
257 |
-
#: ../admin/lib/nginx-general.php:145
|
258 |
-
msgid "Purge Homepage:"
|
259 |
-
msgstr ""
|
260 |
-
|
261 |
-
#: ../admin/lib/nginx-general.php:149
|
262 |
-
msgid "when a post/page/custom post is modified or added."
|
263 |
-
msgstr ""
|
264 |
-
|
265 |
-
#: ../admin/lib/nginx-general.php:153 ../admin/lib/nginx-general.php:216
|
266 |
-
msgid ""
|
267 |
-
"when a <strong>post</strong> (or page/custom post) is <strong>modified</"
|
268 |
-
"strong> or <strong>added</strong>."
|
269 |
-
msgstr ""
|
270 |
-
|
271 |
-
#: ../admin/lib/nginx-general.php:158
|
272 |
-
msgid "when an existing post/page/custom post is modified."
|
273 |
-
msgstr ""
|
274 |
-
|
275 |
-
#: ../admin/lib/nginx-general.php:162 ../admin/lib/nginx-general.php:225
|
276 |
-
msgid ""
|
277 |
-
"when a <strong>published post</strong> (or page/custom post) is "
|
278 |
-
"<strong>trashed</strong>."
|
279 |
-
msgstr ""
|
280 |
-
|
281 |
-
#: ../admin/lib/nginx-general.php:170
|
282 |
-
msgid "Purge Post/Page/Custom Post Type:"
|
283 |
-
msgstr ""
|
284 |
-
|
285 |
-
#: ../admin/lib/nginx-general.php:175
|
286 |
-
msgid "when a post/page/custom post is published."
|
287 |
-
msgstr ""
|
288 |
-
|
289 |
-
#: ../admin/lib/nginx-general.php:179
|
290 |
-
msgid "when a <strong>post</strong> is <strong>published</strong>."
|
291 |
-
msgstr ""
|
292 |
-
|
293 |
-
#: ../admin/lib/nginx-general.php:184
|
294 |
-
msgid "when a comment is approved/published."
|
295 |
-
msgstr ""
|
296 |
-
|
297 |
-
#: ../admin/lib/nginx-general.php:188 ../admin/lib/nginx-general.php:235
|
298 |
-
msgid "when a <strong>comment</strong> is <strong>approved/published</strong>."
|
299 |
-
msgstr ""
|
300 |
-
|
301 |
-
#: ../admin/lib/nginx-general.php:193
|
302 |
-
msgid "when a comment is unapproved/deleted."
|
303 |
-
msgstr ""
|
304 |
-
|
305 |
-
#: ../admin/lib/nginx-general.php:197 ../admin/lib/nginx-general.php:244
|
306 |
-
msgid "when a <strong>comment</strong> is <strong>unapproved/deleted</strong>."
|
307 |
-
msgstr ""
|
308 |
-
|
309 |
-
#: ../admin/lib/nginx-general.php:206
|
310 |
-
msgid "Purge Archives:"
|
311 |
-
msgstr ""
|
312 |
-
|
313 |
-
#: ../admin/lib/nginx-general.php:207
|
314 |
-
msgid "(date, category, tag, author, custom taxonomies)"
|
315 |
-
msgstr ""
|
316 |
-
|
317 |
-
#: ../admin/lib/nginx-general.php:212
|
318 |
-
msgid "when an post/page/custom post is modified or added.</span>"
|
319 |
-
msgstr ""
|
320 |
-
|
321 |
-
#: ../admin/lib/nginx-general.php:221
|
322 |
-
msgid "when an existing post/page/custom post is trashed.</span>"
|
323 |
-
msgstr ""
|
324 |
-
|
325 |
-
#: ../admin/lib/nginx-general.php:231
|
326 |
-
msgid "when a comment is approved/published.</span>"
|
327 |
-
msgstr ""
|
328 |
-
|
329 |
-
#: ../admin/lib/nginx-general.php:240
|
330 |
-
msgid "when a comment is unapproved/deleted.</span>"
|
331 |
-
msgstr ""
|
332 |
-
|
333 |
-
#: ../admin/lib/nginx-general.php:259
|
334 |
-
msgid "Nginx Map"
|
335 |
-
msgstr ""
|
336 |
-
|
337 |
-
#: ../admin/lib/nginx-general.php:263
|
338 |
-
#, php-format
|
339 |
-
msgid ""
|
340 |
-
"Can't write on map file.<br /><br />Check you have write permission on "
|
341 |
-
"<strong>%s</strong>"
|
342 |
-
msgstr ""
|
343 |
-
|
344 |
-
#: ../admin/lib/nginx-general.php:268
|
345 |
-
msgid ""
|
346 |
-
"Nginx Map path to include in nginx settings<br /><small>(recommended)</small>"
|
347 |
-
msgstr ""
|
348 |
-
|
349 |
-
#: ../admin/lib/nginx-general.php:274
|
350 |
-
msgid ""
|
351 |
-
"Or,<br />Text to manually copy and paste in nginx settings<br /><small>(if "
|
352 |
-
"your network is small and new sites are not added frequently)</small>"
|
353 |
-
msgstr ""
|
354 |
-
|
355 |
-
#: ../admin/lib/nginx-general.php:286
|
356 |
-
msgid "Logging Options"
|
357 |
-
msgstr ""
|
358 |
-
|
359 |
-
#: ../admin/lib/nginx-general.php:304
|
360 |
-
#, php-format
|
361 |
-
msgid ""
|
362 |
-
"Can't write on log file.<br /><br />Check you have write permission on "
|
363 |
-
"<strong>%s</strong>"
|
364 |
-
msgstr ""
|
365 |
-
|
366 |
-
#: ../admin/lib/nginx-general.php:310
|
367 |
-
msgid "Logs path"
|
368 |
-
msgstr ""
|
369 |
-
|
370 |
-
#: ../admin/lib/nginx-general.php:314
|
371 |
-
msgid "View Log"
|
372 |
-
msgstr ""
|
373 |
-
|
374 |
-
#: ../admin/lib/nginx-general.php:315
|
375 |
-
msgid "Log"
|
376 |
-
msgstr ""
|
377 |
-
|
378 |
-
#: ../admin/lib/nginx-general.php:318
|
379 |
-
msgid "Log level"
|
380 |
-
msgstr ""
|
381 |
-
|
382 |
-
#: ../admin/lib/nginx-general.php:321
|
383 |
-
msgid "None"
|
384 |
-
msgstr ""
|
385 |
-
|
386 |
-
#: ../admin/lib/nginx-general.php:322
|
387 |
-
msgid "Info"
|
388 |
-
msgstr ""
|
389 |
-
|
390 |
-
#: ../admin/lib/nginx-general.php:323
|
391 |
-
msgid "Warning"
|
392 |
-
msgstr ""
|
393 |
-
|
394 |
-
#: ../admin/lib/nginx-general.php:324
|
395 |
-
msgid "Error"
|
396 |
-
msgstr ""
|
397 |
-
|
398 |
-
#: ../admin/lib/nginx-general.php:329
|
399 |
-
msgid "Max log file size"
|
400 |
-
msgstr ""
|
401 |
-
|
402 |
-
#: ../admin/lib/nginx-general.php:331
|
403 |
-
msgid "Mb"
|
404 |
-
msgstr ""
|
405 |
-
|
406 |
-
#: ../admin/lib/nginx-general.php:342
|
407 |
-
msgid "Save All Changes"
|
408 |
-
msgstr ""
|
409 |
-
|
410 |
-
#: ../admin/lib/nginx-sidebar.php:8
|
411 |
-
msgid "Need Help?"
|
412 |
-
msgstr ""
|
413 |
-
|
414 |
-
#: ../admin/lib/nginx-sidebar.php:11
|
415 |
-
#, php-format
|
416 |
-
msgid "Please use our <a href=\"%s\">free support forum</a>."
|
417 |
-
msgstr ""
|
418 |
-
|
419 |
-
#: ../admin/lib/nginx-sidebar.php:17
|
420 |
-
msgid "Getting Social is Good"
|
421 |
-
msgstr ""
|
422 |
-
|
423 |
-
#: ../admin/lib/nginx-sidebar.php:20
|
424 |
-
msgid "Become a fan on Facebook"
|
425 |
-
msgstr ""
|
426 |
-
|
427 |
-
#: ../admin/lib/nginx-sidebar.php:21
|
428 |
-
msgid "Follow us on Twitter"
|
429 |
-
msgstr ""
|
430 |
-
|
431 |
-
#: ../admin/lib/nginx-sidebar.php:22
|
432 |
-
msgid "Add to Circle"
|
433 |
-
msgstr ""
|
434 |
-
|
435 |
-
#: ../admin/lib/nginx-sidebar.php:23
|
436 |
-
msgid "Subscribe to our feeds"
|
437 |
-
msgstr ""
|
438 |
-
|
439 |
-
#: ../admin/lib/nginx-sidebar.php:29
|
440 |
-
msgid "Useful Links"
|
441 |
-
msgstr ""
|
442 |
-
|
443 |
-
#: ../admin/lib/nginx-sidebar.php:34
|
444 |
-
msgid "WordPress-Nginx Solutions"
|
445 |
-
msgstr ""
|
446 |
-
|
447 |
-
#: ../admin/lib/nginx-sidebar.php:37
|
448 |
-
msgid "WordPress Theme Devleopment"
|
449 |
-
msgstr ""
|
450 |
-
|
451 |
-
#: ../admin/lib/nginx-sidebar.php:40
|
452 |
-
msgid "WordPress Plugin Development"
|
453 |
-
msgstr ""
|
454 |
-
|
455 |
-
#: ../admin/lib/nginx-sidebar.php:43
|
456 |
-
msgid "WordPress Consultancy"
|
457 |
-
msgstr ""
|
458 |
-
|
459 |
-
#: ../admin/lib/nginx-sidebar.php:46
|
460 |
-
msgid "easyengine (ee)"
|
461 |
-
msgstr ""
|
462 |
-
|
463 |
-
#: ../admin/lib/nginx-sidebar.php:53
|
464 |
-
msgid "Click to toggle"
|
465 |
-
msgstr ""
|
466 |
-
|
467 |
-
#: ../admin/lib/nginx-sidebar.php:54
|
468 |
-
msgid "Latest News"
|
469 |
-
msgstr ""
|
470 |
-
|
471 |
-
#: ../admin/lib/nginx-sidebar.php:55
|
472 |
-
msgid "Loading..."
|
473 |
-
msgstr ""
|
474 |
-
|
475 |
-
#: ../admin/lib/nginx-support.php:8
|
476 |
-
msgid "Support Forums"
|
477 |
-
msgstr ""
|
478 |
-
|
479 |
-
#: ../admin/lib/nginx-support.php:13
|
480 |
-
msgid "Need Help!, ask your queries on our forums:"
|
481 |
-
msgstr ""
|
482 |
-
|
483 |
-
#: ../admin/lib/nginx-support.php:16
|
484 |
-
msgid "Free Support"
|
485 |
-
msgstr ""
|
486 |
-
|
487 |
-
#: ../admin/lib/nginx-support.php:18
|
488 |
-
msgid "Free Support Forum"
|
489 |
-
msgstr ""
|
490 |
-
|
491 |
-
#: ../admin/lib/nginx-support.php:18 ../admin/lib/nginx-support.php:24
|
492 |
-
msgid "Link to forum"
|
493 |
-
msgstr ""
|
494 |
-
|
495 |
-
#: ../admin/lib/nginx-support.php:22
|
496 |
-
msgid "Premium Support"
|
497 |
-
msgstr ""
|
498 |
-
|
499 |
-
#: ../admin/lib/nginx-support.php:24
|
500 |
-
msgid "Premium Support Forum"
|
501 |
-
msgstr ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/nginx-helper.php
DELETED
@@ -1,400 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/*
|
3 |
-
Plugin Name: Nginx Helper
|
4 |
-
Plugin URI: https://rtcamp.com/nginx-helper/
|
5 |
-
Description: Cleans nginx's fastcgi/proxy cache whenever a post is edited/published. Also does few more things.
|
6 |
-
Version: 1.8.13
|
7 |
-
Author: rtCamp
|
8 |
-
Author URI: https://rtcamp.com
|
9 |
-
Text Domain: nginx-helper
|
10 |
-
Requires at least: 3.0
|
11 |
-
Tested up to: 4.2
|
12 |
-
*/
|
13 |
-
|
14 |
-
namespace rtCamp\WP\Nginx {
|
15 |
-
define('rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH', plugin_dir_path(__FILE__));
|
16 |
-
define('rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_URL', plugin_dir_url(__FILE__));
|
17 |
-
|
18 |
-
class Helper {
|
19 |
-
|
20 |
-
var $minium_WP = '3.0';
|
21 |
-
var $options = null;
|
22 |
-
var $plugin_name = 'nginx-helper';
|
23 |
-
const WP_CLI_COMMAND = 'nginx-helper';
|
24 |
-
|
25 |
-
function __construct() {
|
26 |
-
|
27 |
-
if (!$this->required_wp_version())
|
28 |
-
if (!$this->required_php_version())
|
29 |
-
return;
|
30 |
-
|
31 |
-
// Load Plugin Text Domain
|
32 |
-
add_action( 'init', array( $this, 'load_plugin_textdomain' ) );
|
33 |
-
|
34 |
-
$this->load_options();
|
35 |
-
$this->plugin_name = plugin_basename(__FILE__);
|
36 |
-
|
37 |
-
register_activation_hook($this->plugin_name, array(&$this, 'activate'));
|
38 |
-
register_deactivation_hook($this->plugin_name, array(&$this, 'deactivate'));
|
39 |
-
|
40 |
-
add_action( 'init', array( &$this, 'start_helper' ), 15 );
|
41 |
-
}
|
42 |
-
|
43 |
-
function start_helper() {
|
44 |
-
|
45 |
-
global $rt_wp_nginx_purger;
|
46 |
-
add_action('shutdown', array(&$this, 'add_timestamps'), 99999);
|
47 |
-
add_action('add_init', array(&$this, 'update_map'));
|
48 |
-
|
49 |
-
add_action('publish_post', array(&$rt_wp_nginx_purger, 'purgePost'), 200, 1);
|
50 |
-
add_action('publish_page', array(&$rt_wp_nginx_purger, 'purgePost'), 200, 1);
|
51 |
-
add_action('wp_insert_comment', array(&$rt_wp_nginx_purger, 'purgePostOnComment'), 200, 2);
|
52 |
-
add_action('transition_comment_status', array(&$rt_wp_nginx_purger, 'purgePostOnCommentChange'), 200, 3);
|
53 |
-
|
54 |
-
$args = array('_builtin' => false);
|
55 |
-
$_rt_custom_post_types = get_post_types($args);
|
56 |
-
if (isset($post_types) && !empty($post_types)) {
|
57 |
-
if ($this->options['rt_wp_custom_post_types'] == true) {
|
58 |
-
foreach ($_rt_custom_post_types as $post_type) {
|
59 |
-
add_action('publish_' . trim($post_type), array(&$rt_wp_nginx_purger, 'purgePost'), 200, 1);
|
60 |
-
}
|
61 |
-
}
|
62 |
-
}
|
63 |
-
|
64 |
-
add_action('transition_post_status', array(&$this, 'set_future_post_option_on_future_status'), 20, 3);
|
65 |
-
add_action('delete_post', array(&$this, 'unset_future_post_option_on_delete'), 20, 1);
|
66 |
-
add_action('nm_check_log_file_size_daily', array(&$rt_wp_nginx_purger, 'checkAndTruncateLogFile'), 100, 1);
|
67 |
-
add_action('edit_attachment', array(&$rt_wp_nginx_purger, 'purgeImageOnEdit'), 100, 1);
|
68 |
-
add_action('wpmu_new_blog', array(&$this, 'update_new_blog_options'), 10, 1);
|
69 |
-
add_action('transition_post_status', array(&$rt_wp_nginx_purger, 'purge_on_post_moved_to_trash'), 20, 3);
|
70 |
-
add_action('edit_term', array(&$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited'), 20, 3);
|
71 |
-
add_action('delete_term', array(&$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited'), 20, 3);
|
72 |
-
add_action('check_ajax_referer', array(&$rt_wp_nginx_purger, 'purge_on_check_ajax_referer'), 20, 2);
|
73 |
-
add_action('admin_init', array(&$this, 'purge_all'));
|
74 |
-
|
75 |
-
// expose action to allow other plugins to purge the cache
|
76 |
-
add_action('rt_nginx_helper_purge_all', array(&$this, 'true_purge_all'));
|
77 |
-
|
78 |
-
// Load WP-CLI command
|
79 |
-
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
80 |
-
require_once RT_WP_NGINX_HELPER_PATH . 'wp-cli.php';
|
81 |
-
\WP_CLI::add_command( self::WP_CLI_COMMAND, 'Nginx_Helper_WP_CLI_Command' );
|
82 |
-
}
|
83 |
-
}
|
84 |
-
|
85 |
-
function activate() {
|
86 |
-
|
87 |
-
$path = $this->functional_asset_path();
|
88 |
-
if (!is_dir($path)) {
|
89 |
-
mkdir($path);
|
90 |
-
}
|
91 |
-
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
92 |
-
rt_wp_nginx_helper_install();
|
93 |
-
}
|
94 |
-
|
95 |
-
function deactivate() {
|
96 |
-
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
97 |
-
rt_wp_nginx_helper_uninstall();
|
98 |
-
}
|
99 |
-
|
100 |
-
function required_wp_version() {
|
101 |
-
|
102 |
-
global $wp_version;
|
103 |
-
$wp_ok = version_compare($wp_version, $this->minium_WP, '>=');
|
104 |
-
if (($wp_ok == FALSE)) {
|
105 |
-
add_action( 'admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
|
106 |
-
add_action( 'network_admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
|
107 |
-
return false;
|
108 |
-
}
|
109 |
-
|
110 |
-
return true;
|
111 |
-
}
|
112 |
-
|
113 |
-
function load_options() {
|
114 |
-
$this->options = get_site_option('rt_wp_nginx_helper_options');
|
115 |
-
}
|
116 |
-
|
117 |
-
function set_future_post_option_on_future_status($new_status, $old_status, $post) {
|
118 |
-
|
119 |
-
global $blog_id, $rt_wp_nginx_purger;
|
120 |
-
if (!$this->options['enable_purge']) {
|
121 |
-
return;
|
122 |
-
}
|
123 |
-
if ($old_status != $new_status && $old_status != 'inherit' && $new_status != 'inherit' && $old_status != 'auto-draft' && $new_status != 'auto-draft' && $new_status != 'publish' && !wp_is_post_revision($post->ID)) {
|
124 |
-
$rt_wp_nginx_purger->log("Purge post on transition post STATUS from " . $old_status . " to " . $new_status);
|
125 |
-
$rt_wp_nginx_purger->purgePost($post->ID);
|
126 |
-
}
|
127 |
-
|
128 |
-
if ($new_status == 'future') {
|
129 |
-
if ($post && $post->post_status == 'future' && ( ( $post->post_type == 'post' || $post->post_type == 'page' ) || ( isset( $this->options['custom_post_types_recognized'] ) && in_array($post->post_type, $this->options['custom_post_types_recognized']) ) )) {
|
130 |
-
$rt_wp_nginx_purger->log("Set/update future_posts option (post id = " . $post->ID . " and blog id = " . $blog_id . ")");
|
131 |
-
$this->options['future_posts'][$blog_id][$post->ID] = strtotime($post->post_date_gmt) + 60;
|
132 |
-
update_site_option("rt_wp_nginx_helper_global_options", $this->options);
|
133 |
-
}
|
134 |
-
}
|
135 |
-
}
|
136 |
-
|
137 |
-
function unset_future_post_option_on_delete($post_id) {
|
138 |
-
|
139 |
-
global $blog_id, $rt_wp_nginx_purger;
|
140 |
-
if (!$this->options['enable_purge']) {
|
141 |
-
return;
|
142 |
-
}
|
143 |
-
if ($post_id && !wp_is_post_revision($post_id)) {
|
144 |
-
|
145 |
-
if (isset($this->options['future_posts'][$blog_id][$post_id]) && count($this->options['future_posts'][$blog_id][$post_id])) {
|
146 |
-
$rt_wp_nginx_purger->log("Unset future_posts option (post id = " . $post_id . " and blog id = " . $blog_id . ")");
|
147 |
-
unset($this->options['future_posts'][$blog_id][$post_id]);
|
148 |
-
update_site_option("rt_wp_nginx_helper_global_options", $this->options);
|
149 |
-
|
150 |
-
if (!count($this->options['future_posts'][$blog_id])) {
|
151 |
-
unset($this->options['future_posts'][$blog_id]);
|
152 |
-
update_site_option("rt_wp_nginx_helper_global_options", $this->options);
|
153 |
-
}
|
154 |
-
}
|
155 |
-
}
|
156 |
-
}
|
157 |
-
|
158 |
-
function update_new_blog_options($blog_id) {
|
159 |
-
global $rt_wp_nginx_purger;
|
160 |
-
include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
|
161 |
-
$rt_wp_nginx_purger->log("New site added (id $blog_id)");
|
162 |
-
$this->update_map();
|
163 |
-
$rt_wp_nginx_purger->log("New site added to nginx map (id $blog_id)");
|
164 |
-
$helper_options = rt_wp_nginx_helper_get_options();
|
165 |
-
update_blog_option($blog_id, "rt_wp_nginx_helper_options", $helper_options);
|
166 |
-
$rt_wp_nginx_purger->log("Default options updated for the new blog (id $blog_id)");
|
167 |
-
}
|
168 |
-
|
169 |
-
function get_map() {
|
170 |
-
if (!$this->options['enable_map']) {
|
171 |
-
return;
|
172 |
-
}
|
173 |
-
|
174 |
-
if (is_multisite()) {
|
175 |
-
|
176 |
-
global $wpdb;
|
177 |
-
|
178 |
-
$rt_all_blogs = $wpdb->get_results($wpdb->prepare("SELECT blog_id, domain, path FROM " . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0'", $wpdb->siteid));
|
179 |
-
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
180 |
-
$rt_domain_map_sites = '';
|
181 |
-
if ($wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtable}'") == $wpdb->dmtable) {
|
182 |
-
$rt_domain_map_sites = $wpdb->get_results("SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY id DESC");
|
183 |
-
}
|
184 |
-
$rt_nginx_map = "";
|
185 |
-
$rt_nginx_map_array = array();
|
186 |
-
|
187 |
-
|
188 |
-
if ($rt_all_blogs)
|
189 |
-
foreach ($rt_all_blogs as $blog) {
|
190 |
-
if (SUBDOMAIN_INSTALL == "yes") {
|
191 |
-
$rt_nginx_map_array[$blog->domain] = $blog->blog_id;
|
192 |
-
} else {
|
193 |
-
if ($blog->blog_id != 1) {
|
194 |
-
$rt_nginx_map_array[$blog->path] = $blog->blog_id;
|
195 |
-
}
|
196 |
-
}
|
197 |
-
}
|
198 |
-
|
199 |
-
if ($rt_domain_map_sites) {
|
200 |
-
foreach ($rt_domain_map_sites as $site) {
|
201 |
-
$rt_nginx_map_array[$site->domain] = $site->blog_id;
|
202 |
-
}
|
203 |
-
}
|
204 |
-
|
205 |
-
foreach ($rt_nginx_map_array as $domain => $domain_id) {
|
206 |
-
$rt_nginx_map .= "\t" . $domain . "\t" . $domain_id . ";\n";
|
207 |
-
}
|
208 |
-
|
209 |
-
return $rt_nginx_map;
|
210 |
-
}
|
211 |
-
}
|
212 |
-
|
213 |
-
function functional_asset_path() {
|
214 |
-
$dir = wp_upload_dir();
|
215 |
-
$path = $dir['basedir'] . '/nginx-helper/';
|
216 |
-
return apply_filters('nginx_asset_path', $path);
|
217 |
-
}
|
218 |
-
|
219 |
-
function functional_asset_url() {
|
220 |
-
$dir = wp_upload_dir();
|
221 |
-
$url = $dir['baseurl'] . '/nginx-helper/';
|
222 |
-
return apply_filters('nginx_asset_url', $url);
|
223 |
-
}
|
224 |
-
|
225 |
-
function update_map() {
|
226 |
-
if (is_multisite()) {
|
227 |
-
$rt_nginx_map = $this->get_map();
|
228 |
-
|
229 |
-
if ($fp = fopen($this->functional_asset_path() . 'map.conf', 'w+')) {
|
230 |
-
fwrite($fp, $rt_nginx_map);
|
231 |
-
fclose($fp);
|
232 |
-
return true;
|
233 |
-
}
|
234 |
-
}
|
235 |
-
}
|
236 |
-
|
237 |
-
function add_timestamps() {
|
238 |
-
if ($this->options['enable_purge'] != 1)
|
239 |
-
return;
|
240 |
-
if ($this->options['enable_stamp'] != 1)
|
241 |
-
return;
|
242 |
-
if (is_admin())
|
243 |
-
return;
|
244 |
-
foreach (headers_list() as $header) {
|
245 |
-
list($key, $value) = explode(':', $header, 2);
|
246 |
-
if ($key == 'Content-Type' && strpos(trim($value), 'text/html') !== 0) {
|
247 |
-
return;
|
248 |
-
}
|
249 |
-
if ($key == 'Content-Type')
|
250 |
-
break;
|
251 |
-
}
|
252 |
-
|
253 |
-
if (defined('DOING_AJAX') && DOING_AJAX)
|
254 |
-
return;
|
255 |
-
$timestamps = "\n<!--" .
|
256 |
-
"Cached using Nginx-Helper on " . current_time('mysql') . ". " .
|
257 |
-
"It took " . get_num_queries() . " queries executed in " . timer_stop() . " seconds." .
|
258 |
-
"-->\n" .
|
259 |
-
"<!--Visit http://wordpress.org/extend/plugins/nginx-helper/faq/ for more details-->";
|
260 |
-
echo $timestamps;
|
261 |
-
}
|
262 |
-
|
263 |
-
function show_notice() {
|
264 |
-
echo '<div class="updated"><p>' . __('Purge initiated', 'nginx-helper') . '</p></div>';
|
265 |
-
}
|
266 |
-
|
267 |
-
function purge_all() {
|
268 |
-
if (!isset($_REQUEST['nginx_helper_action']))
|
269 |
-
return;
|
270 |
-
|
271 |
-
if (!current_user_can('manage_options'))
|
272 |
-
wp_die('Sorry, you do not have the necessary privileges to edit these options.');
|
273 |
-
|
274 |
-
$action = $_REQUEST['nginx_helper_action'];
|
275 |
-
|
276 |
-
if ($action == 'done') {
|
277 |
-
add_action('admin_notices', array(&$this, 'show_notice'));
|
278 |
-
add_action('network_admin_notices', array(&$this, 'show_notice'));
|
279 |
-
return;
|
280 |
-
}
|
281 |
-
|
282 |
-
check_admin_referer('nginx_helper-purge_all');
|
283 |
-
|
284 |
-
switch ($action) {
|
285 |
-
case 'purge':
|
286 |
-
$this->true_purge_all();
|
287 |
-
break;
|
288 |
-
}
|
289 |
-
wp_redirect( esc_url_raw( add_query_arg( array( 'nginx_helper_action' => 'done' ) ) ) );
|
290 |
-
}
|
291 |
-
|
292 |
-
function true_purge_all() {
|
293 |
-
global $rt_wp_nginx_purger;
|
294 |
-
$rt_wp_nginx_purger->true_purge_all();
|
295 |
-
}
|
296 |
-
|
297 |
-
/**
|
298 |
-
* Load the translation file for current language.
|
299 |
-
*/
|
300 |
-
function load_plugin_textdomain() {
|
301 |
-
load_plugin_textdomain( 'nginx-helper', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
|
302 |
-
}
|
303 |
-
}
|
304 |
-
}
|
305 |
-
|
306 |
-
namespace {
|
307 |
-
|
308 |
-
if (!defined('RT_WP_NGINX_HELPER_CACHE_PATH')) {
|
309 |
-
define('RT_WP_NGINX_HELPER_CACHE_PATH', '/var/run/nginx-cache');
|
310 |
-
}
|
311 |
-
global $current_blog;
|
312 |
-
|
313 |
-
if (is_admin()) {
|
314 |
-
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . '/admin/admin.php');
|
315 |
-
$rtwpAdminPanel = new \rtCamp\WP\Nginx\Admin();
|
316 |
-
}
|
317 |
-
|
318 |
-
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'purger.php');
|
319 |
-
require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'compatibility.php');
|
320 |
-
|
321 |
-
global $rt_wp_nginx_helper, $rt_wp_nginx_purger, $rt_wp_nginx_compatibility;
|
322 |
-
$rt_wp_nginx_helper = new \rtCamp\WP\Nginx\Helper;
|
323 |
-
$rt_wp_nginx_purger = new \rtCamp\WP\Nginx\Purger;
|
324 |
-
$rt_wp_nginx_compatibility = namespace\rtCamp\WP\Nginx\Compatibility::instance();
|
325 |
-
if ($rt_wp_nginx_compatibility->haveNginx() && !function_exists('wp_redirect')) {
|
326 |
-
|
327 |
-
function wp_redirect($location, $status = 302) {
|
328 |
-
$location = apply_filters('wp_redirect', $location, $status);
|
329 |
-
|
330 |
-
if (empty($location)) {
|
331 |
-
return false;
|
332 |
-
}
|
333 |
-
|
334 |
-
$status = apply_filters('wp_redirect_status', $status, $location);
|
335 |
-
if ($status < 300 || $status > 399) {
|
336 |
-
$status = 302;
|
337 |
-
}
|
338 |
-
|
339 |
-
if (function_exists('wp_sanitize_redirect')) {
|
340 |
-
$location = wp_sanitize_redirect($location);
|
341 |
-
}
|
342 |
-
header('Location: ' . $location, true, $status);
|
343 |
-
}
|
344 |
-
|
345 |
-
}
|
346 |
-
|
347 |
-
// Add settings link on plugin page
|
348 |
-
function nginx_settings_link($links) {
|
349 |
-
if (is_network_admin()) {
|
350 |
-
$u = 'settings.php';
|
351 |
-
} else {
|
352 |
-
$u = 'options-general.php';
|
353 |
-
}
|
354 |
-
$settings_link = '<a href="' . $u . '?page=nginx">' . __('Settings', 'nginx-helper') . '</a>';
|
355 |
-
array_unshift($links, $settings_link);
|
356 |
-
return $links;
|
357 |
-
}
|
358 |
-
|
359 |
-
if (is_multisite()) {
|
360 |
-
add_filter("network_admin_plugin_action_links_" . plugin_basename(__FILE__), 'nginx_settings_link');
|
361 |
-
} else {
|
362 |
-
add_filter("plugin_action_links_" . plugin_basename(__FILE__), 'nginx_settings_link');
|
363 |
-
}
|
364 |
-
|
365 |
-
function get_feeds($feed_url = 'http://rtcamp.com/blog/feed/') {
|
366 |
-
// Get RSS Feed(s)
|
367 |
-
require_once( ABSPATH . WPINC . '/feed.php' );
|
368 |
-
$maxitems = 0;
|
369 |
-
// Get a SimplePie feed object from the specified feed source.
|
370 |
-
$rss = fetch_feed($feed_url);
|
371 |
-
if (!is_wp_error($rss)) { // Checks that the object is created correctly
|
372 |
-
// Figure out how many total items there are, but limit it to 5.
|
373 |
-
$maxitems = $rss->get_item_quantity(5);
|
374 |
-
|
375 |
-
// Build an array of all the items, starting with element 0 (first element).
|
376 |
-
$rss_items = $rss->get_items(0, $maxitems);
|
377 |
-
} ?>
|
378 |
-
<ul role="list"><?php
|
379 |
-
if ($maxitems == 0) {
|
380 |
-
echo '<li role="listitem">' . __('No items', 'nginx-helper') . '.</li>';
|
381 |
-
} else {
|
382 |
-
// Loop through each feed item and display each item as a hyperlink.
|
383 |
-
foreach ($rss_items as $item) { ?>
|
384 |
-
<li role="listitem">
|
385 |
-
<a href='<?php echo $item->get_permalink(); ?>' title='<?php echo __('Posted ', 'nginx-helper') . $item->get_date('j F Y | g:i a'); ?>'><?php echo $item->get_title(); ?></a>
|
386 |
-
</li><?php
|
387 |
-
}
|
388 |
-
} ?>
|
389 |
-
</ul><?php
|
390 |
-
}
|
391 |
-
|
392 |
-
function fetch_feeds() {
|
393 |
-
if (isset($_GET['get_feeds']) && $_GET['get_feeds'] == '1') {
|
394 |
-
get_feeds();
|
395 |
-
die();
|
396 |
-
}
|
397 |
-
}
|
398 |
-
|
399 |
-
add_action('init', 'fetch_feeds');
|
400 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/readme.txt
DELETED
@@ -1,297 +0,0 @@
|
|
1 |
-
=== Nginx Helper ===
|
2 |
-
Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, Darren Slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx
|
3 |
-
Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, rewrite, permalinks
|
4 |
-
Requires at least: 3.0
|
5 |
-
Tested up to: 4.2
|
6 |
-
Stable tag: 1.8.13
|
7 |
-
License: GPLv2 or later (of-course)
|
8 |
-
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
-
Donate Link: http://rtcamp.com/donate/
|
10 |
-
|
11 |
-
Cleans nginx's fastcgi/proxy cache whenever a post is edited/published. Also does a few more things.
|
12 |
-
|
13 |
-
== Description ==
|
14 |
-
|
15 |
-
1. Removes `index.php` from permalinks when using WordPress with nginx.
|
16 |
-
1. Adds support for nginx fastcgi_cache_purge & proxy_cache_purge directive from [module](https://github.com/FRiCKLE/ngx_cache_purge "ngx_cache_purge module"). Provides settings so you can customize purging rules.
|
17 |
-
1. Adds support for nginx `map{..}` on a WordPress-multisite network installation. Using it, Nginx can serve PHP file uploads even if PHP/MySQL crashes. Please check the tutorial list below for related Nginx configurations.
|
18 |
-
|
19 |
-
= Tutorials =
|
20 |
-
|
21 |
-
You will need to follow one or more tutorials below to get desired functionality:
|
22 |
-
|
23 |
-
* [Nginx Map + WordPress-Multisite + Static Files Handling](http://rtcamp.com/tutorials/nginx-maps-wordpress-multisite-static-files-handling/)
|
24 |
-
* [Nginx + WordPress + fastcgi_purge_cache](http://rtcamp.com/tutorials/wordpress-nginx-fastcgi-cache-purge-conditional/)
|
25 |
-
* [Nginx + WordPress-Multisite (Subdirectories) + fastcgi_purge_cache](http://rtcamp.com/tutorials/wordpress-multisite-subdirectories-nginx-fastcgi-cache-purge/)
|
26 |
-
* [Nginx + WordPress-Multisite (Subdomains/domain-mapping) + fastcgi_purge_cache](http://rtcamp.com/tutorials/wordpress-multisite-subdomains-domain-mapping-nginx-fastcgi-cache-purge/)
|
27 |
-
* [Other WordPress-Nginx Tutorials](http://rtcamp.com/wordpress-nginx/tutorials/)
|
28 |
-
|
29 |
-
|
30 |
-
== Installation ==
|
31 |
-
|
32 |
-
Automatic Installation
|
33 |
-
|
34 |
-
1. Log in to your WordPress admin panel, navigate to the Plugins menu and click Add New.
|
35 |
-
1. In the search field type “Nginx Helper” and click Search Plugins. From the search results, pick Nginx Helper and click Install Now. Wordpress will ask you to confirm to complete the installation.
|
36 |
-
|
37 |
-
Manual Installation
|
38 |
-
|
39 |
-
1. Extract the zip file.
|
40 |
-
1. Upload them to `/wp-content/plugins/` directory on your WordPress installation.
|
41 |
-
1. Then activate the Plugin from Plugins page.
|
42 |
-
|
43 |
-
For proper configuration, check out our **tutorial list** in the [Description tab](http://wordpress.org/extend/plugins/nginx-helper).
|
44 |
-
|
45 |
-
== Frequently Asked Questions ==
|
46 |
-
|
47 |
-
**Important** - Please refer to [http://rtcamp.com/nginx-helper/faq](http://rtcamp.com/nginx-helper/faq) for up-to-date FAQs.
|
48 |
-
|
49 |
-
= FAQ - Installation/Comptability =
|
50 |
-
|
51 |
-
**Q. Will this work out of the box?**
|
52 |
-
|
53 |
-
No. You need to make some changes at the Nginx end. Please check our [tutorial list](http://rtcamp.com/wordpress-nginx/tutorials).
|
54 |
-
|
55 |
-
= FAQ - Nginx Fastcgi Cache Purge =
|
56 |
-
|
57 |
-
**Q. There's a 'purge all' button? Does it purge the whole site?**
|
58 |
-
|
59 |
-
Yes, it does. It physically empties the cache directory. It is set by default to `/var/run/nginx-cache/`.
|
60 |
-
|
61 |
-
If your cache directory is different, you can override this in your wp-config.php by adding
|
62 |
-
`define('RT_WP_NGINX_HELPER_CACHE_PATH','/var/run/nginx-cache/');`
|
63 |
-
|
64 |
-
Replace the path with your own.
|
65 |
-
|
66 |
-
**Q. Does it work for custom posts and taxonomies?**
|
67 |
-
|
68 |
-
Yes. It handles all post-types the same way.
|
69 |
-
|
70 |
-
**Q. How do I know my Nginx config is correct for fastcgi purging?**
|
71 |
-
|
72 |
-
Manually purging any page from the cache, by following instructions in the previous answer.
|
73 |
-
|
74 |
-
Version 1.3.4 onwards, Nginx Helper adds a comment at the end of the HTML source ('view source' in your favourite browser):
|
75 |
-
`<!--Cached using Nginx-Helper on 2012-10-08 07:01:45. It took 42 queries executed in 0.280 seconds.-->`. This shows the time when the page was last cached. This date/time will be reset whenever this page is purged and refreshed in the cache. Just check this comment before and after a manual purge.
|
76 |
-
|
77 |
-
As long as you don't purge the page (or make changes that purge it from the cache), the timestamp will remain as is, even if you keep refreshing the page. This means the page was served from the cache and it's working!
|
78 |
-
|
79 |
-
The rest shows you the database queries and time saved on loading this page. (This would have been the additional resource load, if you weren't using fast-cgi-cache.)
|
80 |
-
|
81 |
-
|
82 |
-
**Q. I need to flush a cached page immediately! How do I do that?**
|
83 |
-
|
84 |
-
Nginx helper plugin handles usual scenarios, when a page in the cache will need purging. For example, when a post is edited or a comment is approved on a post.
|
85 |
-
|
86 |
-
To purge a page immediately, follow these instructions:
|
87 |
-
|
88 |
-
* Let's say we have a page at the following domain: http://yoursite.com/about.
|
89 |
-
* Between the domain name and the rest of the URL, insert '/purge/'.
|
90 |
-
* So, in the above example, the purge URL will be http://yoursite.com/purge/about.
|
91 |
-
* Just open this in a browser and the page will be purged instantly.
|
92 |
-
* Needless to say, this won't work, if you have a page or taxonomy called 'purge'.
|
93 |
-
|
94 |
-
|
95 |
-
= FAQ - Nginx Map =
|
96 |
-
|
97 |
-
**Q. My multisite already uses `WPMU_ACCEL_REDIRECT`. Do I still need Nginx Map?**
|
98 |
-
|
99 |
-
Definitely. `WPMU_ACCEL_REDIRECT` reduces the load on PHP, but it still ask WordPress i.e. PHP/MySQL to do some work for static files e.g. images in your post. Nginx map lets nginx handle files on its own bypassing wordpress which gives you much better performance without using a CDN.
|
100 |
-
|
101 |
-
**Q. I am using X plugin. Will it work on Nginx?**
|
102 |
-
|
103 |
-
Most likely yes. A wordpress plugin, if not using explicitly any Apache-only mod, should work on Nginx. Some plugin may need some extra work.
|
104 |
-
|
105 |
-
|
106 |
-
= Still need help! =
|
107 |
-
|
108 |
-
Please post your problem in [our free support forum](http://community.rtcamp.com/c/wordpress-nginx).
|
109 |
-
|
110 |
-
== Screenshots ==
|
111 |
-
1. Nginx plugin settings
|
112 |
-
2. Remaining settings
|
113 |
-
|
114 |
-
== Changelog ==
|
115 |
-
|
116 |
-
= 1.8.13 =
|
117 |
-
Fixed PHP notice for an undefined index when "Enable Logging" is not set.
|
118 |
-
|
119 |
-
= 1.8.12 =
|
120 |
-
Updated readme and changelog
|
121 |
-
|
122 |
-
= 1.8.11 =
|
123 |
-
Fix url escaping [#82](https://github.com/rtCamp/nginx-helper/pull/82) - by
|
124 |
-
[javisperez](https://github.com/javisperez)
|
125 |
-
|
126 |
-
= 1.8.10 =
|
127 |
-
* Security bug fix
|
128 |
-
|
129 |
-
= 1.8.9 =
|
130 |
-
* Default setting fix and wp-cli example correction - by [bcole808](https://profiles.wordpress.org/bcole808/)
|
131 |
-
|
132 |
-
= 1.8.8 =
|
133 |
-
* Added option to purge cache without nginx purge module - by [bcole808](https://profiles.wordpress.org/bcole808/)
|
134 |
-
|
135 |
-
= 1.8.7 =
|
136 |
-
* Added action `rt_nginx_helper_purge_all` to purge cache from other plugins - by [gungeekatx](https://profiles.wordpress.org/gungeekatx/)
|
137 |
-
|
138 |
-
= 1.8.6 =
|
139 |
-
* Removed wercker.yml from plugin zip/svn.
|
140 |
-
* Updated readme
|
141 |
-
|
142 |
-
= 1.8.5 =
|
143 |
-
* Added WP_CLI support - by [Udit Desai](https://profiles.wordpress.org/desaiuditd/)
|
144 |
-
|
145 |
-
= 1.8.4 =
|
146 |
-
* Fix undefined index issue and correct "purge_archive_on_del" key
|
147 |
-
|
148 |
-
= 1.8.3 =
|
149 |
-
* Tested with WordPress 4.0
|
150 |
-
* Fix issue #69
|
151 |
-
|
152 |
-
= 1.8.1 =
|
153 |
-
* Tested with wordpress 3.9.1
|
154 |
-
* Fix confilct with Mailchimp's Social plugin
|
155 |
-
|
156 |
-
= 1.8 =
|
157 |
-
* New admin UI
|
158 |
-
* Fix missing wp_sanitize_redirect function call
|
159 |
-
|
160 |
-
= 1.7.6 =
|
161 |
-
* Update Backend UI
|
162 |
-
* Added Language Support
|
163 |
-
|
164 |
-
= 1.7.5 =
|
165 |
-
* Fixed option name mismatch issue to purge homepage on delete.
|
166 |
-
|
167 |
-
= 1.7.4 =
|
168 |
-
* Disable purge and stamp by default.
|
169 |
-
|
170 |
-
= 1.7.3 =
|
171 |
-
* Suppressed `unlink` related error-messages which can be safely ignored.
|
172 |
-
* Fixed a bug in purge-all option.
|
173 |
-
|
174 |
-
= 1.7.2 =
|
175 |
-
* [pjv](http://profiles.wordpress.org/pjv/) fixed bug in logging file.
|
176 |
-
|
177 |
-
= 1.7.1 =
|
178 |
-
* Fixes bug in true purge and admin screen.
|
179 |
-
|
180 |
-
= 1.7 =
|
181 |
-
* True full cache purge added.
|
182 |
-
* Map file location changed to uploads' directory to fix http://rtcamp.com/support/topic/plugin-update-removes-map-file/
|
183 |
-
* Log file location also changed to uploads' directory.
|
184 |
-
|
185 |
-
= 1.6.13 =
|
186 |
-
* [pjv](http://profiles.wordpress.org/pjv/) changed the way home URL is accessed. Instead of site option, the plugin now uses home_URL() function.
|
187 |
-
|
188 |
-
= 1.6.12 =
|
189 |
-
* [telofy](http://wordpress.org/support/profile/telofy) added purging of atom and RDF feeds.
|
190 |
-
|
191 |
-
= 1.6.11 =
|
192 |
-
* Removed comments from Admin screens since, it was interfering with media uploads in 3.5 up.
|
193 |
-
|
194 |
-
= 1.6.10 =
|
195 |
-
* Cleaned up code.
|
196 |
-
* Added credits for code.
|
197 |
-
* Improved attachment purging.
|
198 |
-
|
199 |
-
= 1.6.9 =
|
200 |
-
* Added Faux to Purge all buttons, to avoid misleading users.
|
201 |
-
|
202 |
-
= 1.6.8 =
|
203 |
-
* [daankortenbach](http://profiles.wordpress.org/daankortenbach) added Purge Cache link to wp-admin bar
|
204 |
-
|
205 |
-
= 1.6.7 =
|
206 |
-
* [jk3us](http://profiles.wordpress.org/jk3us) added better content-type detection for cache verification comments
|
207 |
-
|
208 |
-
= 1.6.6 =
|
209 |
-
* [darren-slatten](http://profiles.wordpress.org/darren-slatten/) added Manual 'Purge all URLs' functionality
|
210 |
-
|
211 |
-
= 1.6.5 =
|
212 |
-
* Fixed typo that interfered with archive purge settings. Thanks to [Daan Kortenbach](http://profiles.wordpress.org/daankortenbach/) for pointing this out.
|
213 |
-
|
214 |
-
= 1.6.4 =
|
215 |
-
* Improved code for map generation to better conventions since the nesting confused some servers.
|
216 |
-
* Added map update process to admin_init for frequent refreshes.
|
217 |
-
|
218 |
-
= 1.6.3 =
|
219 |
-
* Fixed duplicate entries.
|
220 |
-
|
221 |
-
= 1.6.2 =
|
222 |
-
* Another bug fix in the revised code for improved multisite and multidomain mapping.
|
223 |
-
|
224 |
-
= 1.6.1 =
|
225 |
-
* Fixed bug in the revised code for improved multisite and multidomain mapping.
|
226 |
-
|
227 |
-
= 1.6 =
|
228 |
-
* Revised code for improved multisite and multidomain mapping.
|
229 |
-
|
230 |
-
= 1.5 =
|
231 |
-
* Timestamp now only gets added to content-type text/html
|
232 |
-
* Added option to toggle timestamp creation
|
233 |
-
|
234 |
-
= 1.4 =
|
235 |
-
* Fixed bug related to nomenclature of comment status that caused purge to fail.
|
236 |
-
|
237 |
-
= 1.3.9 =
|
238 |
-
* Removed extraneous headers.
|
239 |
-
|
240 |
-
= 1.3.8 =
|
241 |
-
|
242 |
-
* Fixed bug in single post/page/post-type purging code. Thanks to Greg for pointing this out here: http://rtcamp.com/support/topic/updating-post-nginx-helper-purge-cache-post/.
|
243 |
-
|
244 |
-
= 1.3.7 =
|
245 |
-
|
246 |
-
* Changed the action hook, back to 'shutdown' from 'wp_footer' to add verification comments.
|
247 |
-
* Added a check to prevent adding comments to ajax requests,
|
248 |
-
|
249 |
-
= 1.3.6 =
|
250 |
-
|
251 |
-
* Changed the action hook, from 'shutdown' to 'wp_footer' to add verification comments. This was interfering with other plugins.
|
252 |
-
|
253 |
-
= 1.3.5 =
|
254 |
-
|
255 |
-
* Improved Readme.
|
256 |
-
* Improved cache verification comments.
|
257 |
-
|
258 |
-
= 1.3.4 =
|
259 |
-
|
260 |
-
* Fixed duplicate entries generated for maps (Harmless, but doesn't look good!)
|
261 |
-
* Added timestamp html comments for cache verification, as described here: http://rtcamp.com/wordpress-nginx/tutorials/checklist/
|
262 |
-
|
263 |
-
= 1.3.3 =
|
264 |
-
|
265 |
-
* Fixed map generation for multi domain installs using domain mapping plugin, where blog ids were not displayed.
|
266 |
-
|
267 |
-
= 1.3.2 =
|
268 |
-
|
269 |
-
* Fixed map generation for multi domain installs with domain mapping plugin.
|
270 |
-
|
271 |
-
= 1.3.1 =
|
272 |
-
|
273 |
-
* Minor fixes for directory structure and file names.
|
274 |
-
|
275 |
-
= 1.3 =
|
276 |
-
|
277 |
-
* Improved Readme.
|
278 |
-
|
279 |
-
= 1.2 =
|
280 |
-
|
281 |
-
* Fixed map generation error.
|
282 |
-
* Fixed purging logic.
|
283 |
-
* Fixed UI where purge settings were lost on disabling and re-enabling purge.
|
284 |
-
* Minor Ui rearrangement.
|
285 |
-
|
286 |
-
= 1.1 =
|
287 |
-
|
288 |
-
* Improved readme.txt. Added Screenshots.
|
289 |
-
|
290 |
-
= 1.0 =
|
291 |
-
|
292 |
-
* First release
|
293 |
-
|
294 |
-
== Upgrade Notice ==
|
295 |
-
|
296 |
-
= 1.8.4 =
|
297 |
-
Fix undefined index issue and correct "purge_archive_on_del" key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/wercker.yml
DELETED
@@ -1,22 +0,0 @@
|
|
1 |
-
box: ubuntu
|
2 |
-
|
3 |
-
build:
|
4 |
-
steps:
|
5 |
-
|
6 |
-
- script:
|
7 |
-
name: placehoder
|
8 |
-
code: echo "nothing to build"
|
9 |
-
|
10 |
-
deploy:
|
11 |
-
steps:
|
12 |
-
# - install-packages:
|
13 |
-
# packages: subversion git wget
|
14 |
-
|
15 |
-
- rtcamp/wordpress-svn:
|
16 |
-
pluginslug: nginx-helper
|
17 |
-
mainfile: nginx-helper.php
|
18 |
-
svnuser: $SVNUSER
|
19 |
-
svnpass: $SVNPASS #wordpress.org password
|
20 |
-
gituser: $GITUSER
|
21 |
-
gitpass: $GITPASS #github.com password
|
22 |
-
gitemail: git@example.com #github.com password
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
trunk/wp-cli.php
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* Created by PhpStorm.
|
4 |
-
* User: udit
|
5 |
-
* Date: 19/3/15
|
6 |
-
* Time: 2:06 PM
|
7 |
-
*/
|
8 |
-
|
9 |
-
/**
|
10 |
-
* Don't load this file directly!
|
11 |
-
*/
|
12 |
-
if ( ! defined( 'ABSPATH' ) ) {
|
13 |
-
exit;
|
14 |
-
}
|
15 |
-
|
16 |
-
if ( ! class_exists( 'Nginx_Helper_WP_CLI_Command' ) ) {
|
17 |
-
|
18 |
-
class Nginx_Helper_WP_CLI_Command extends WP_CLI_Command {
|
19 |
-
|
20 |
-
/**
|
21 |
-
* Subcommand to purge all cache from Nginx
|
22 |
-
*
|
23 |
-
* Examples:
|
24 |
-
* wp nginx-helper purge-all
|
25 |
-
*
|
26 |
-
* @subcommand purge-all
|
27 |
-
*/
|
28 |
-
public function purge_all( $args, $assoc_args ) {
|
29 |
-
global $rt_wp_nginx_purger;
|
30 |
-
$rt_wp_nginx_purger->true_purge_all();
|
31 |
-
$message = __( 'Purged Everything!' );
|
32 |
-
WP_CLI::success( $message );
|
33 |
-
}
|
34 |
-
|
35 |
-
}
|
36 |
-
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|