added redis contrib module
This commit is contained in:
parent
1eb61fe020
commit
18f4aba146
1
sites/all/modules/contrib/dev/redis/.gitignore
vendored
Normal file
1
sites/all/modules/contrib/dev/redis/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
predis
|
339
sites/all/modules/contrib/dev/redis/LICENSE.txt
Normal file
339
sites/all/modules/contrib/dev/redis/LICENSE.txt
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
37
sites/all/modules/contrib/dev/redis/README.PhpRedis.txt
Normal file
37
sites/all/modules/contrib/dev/redis/README.PhpRedis.txt
Normal file
@ -0,0 +1,37 @@
|
||||
PhpRedis cache backend
|
||||
======================
|
||||
|
||||
This client, for now, is only able to use the PhpRedis extension.
|
||||
|
||||
Get PhpRedis
|
||||
------------
|
||||
|
||||
You can download this library at:
|
||||
|
||||
https://github.com/phpredis/phpredis
|
||||
|
||||
Most common Linux distribution should now have packages for this PHP extension
|
||||
however if that's not the case for the one you use, use the above link to find
|
||||
the release source download links and follow the provided instructions in order
|
||||
to compile it for your system.
|
||||
|
||||
Default behavior is to connect via tcp://localhost:6379 but you might want to
|
||||
connect differently.
|
||||
|
||||
Connect via UNIX socket
|
||||
-----------------------
|
||||
|
||||
Just add this line to your settings.php file:
|
||||
|
||||
$conf['redis_cache_socket'] = '/tmp/redis.sock';
|
||||
|
||||
Don't forget to change the path depending on your operating system and Redis
|
||||
server configuration.
|
||||
|
||||
Connect to a remote host and database
|
||||
-------------------------------------
|
||||
|
||||
See README.txt file.
|
||||
|
||||
For this particular implementation, host settings are overridden by the
|
||||
UNIX socket parameter.
|
60
sites/all/modules/contrib/dev/redis/README.Predis.txt
Normal file
60
sites/all/modules/contrib/dev/redis/README.Predis.txt
Normal file
@ -0,0 +1,60 @@
|
||||
Predis cache backend
|
||||
====================
|
||||
|
||||
This module will work with the Predis 1.x version. Any earlier versions
|
||||
are unsupported.
|
||||
|
||||
This client, for now, is only able to use the Predis PHP library.
|
||||
|
||||
The Predis library requires PHP 5.3 minimum. If your hosted environment does
|
||||
not ships with at least PHP 5.3, please do not use this cache backend.
|
||||
|
||||
Please consider using an OPCode cache such as APC. Predis is a good and fully
|
||||
featured API, the cost is that the code is a lot more than a single file in
|
||||
opposition to some other backends such as the APC one.
|
||||
|
||||
Get Predis
|
||||
----------
|
||||
|
||||
You can download this library at:
|
||||
|
||||
https://github.com/nrk/predis
|
||||
|
||||
This file explains how to install the Predis library and the Drupal cache
|
||||
backend. If you are an advanced Drupal integrator, please consider the fact
|
||||
that you can easily change all the pathes. Pathes used in this file are
|
||||
likely to be default for non advanced users.
|
||||
|
||||
Download and install library
|
||||
----------------------------
|
||||
|
||||
Once done, you either have to clone it into:
|
||||
|
||||
sites/all/libraries/predis
|
||||
|
||||
So that you have the following directory tree:
|
||||
|
||||
sites/all/libraries/predis/src/ # Where the PHP code stands
|
||||
|
||||
Or, any other place in order to share it:
|
||||
For example, from your install profiles libraries folder:
|
||||
|
||||
profiles/example/libraries/predis
|
||||
|
||||
If you choose this solution, you have to alter a bit your $conf array into
|
||||
the settings.php file as this:
|
||||
|
||||
define('PREDIS_BASE_PATH', DRUPAL_ROOT . '/profiles/example/libraries/predis/');
|
||||
|
||||
Connect to a remote host and database
|
||||
-------------------------------------
|
||||
|
||||
See README.txt file.
|
||||
|
||||
Advanced configuration (PHP expert)
|
||||
-----------------------------------
|
||||
|
||||
Best solution is, whatever is the place where you put the Predis library, that
|
||||
you set up a fully working autoloader able to use it. The one being used by the
|
||||
Redis module is a default fallback and will naturally being appened to the SPL
|
||||
autoloader stack.
|
40
sites/all/modules/contrib/dev/redis/README.testing.txt
Normal file
40
sites/all/modules/contrib/dev/redis/README.testing.txt
Normal file
@ -0,0 +1,40 @@
|
||||
Redis module testing
|
||||
====================
|
||||
|
||||
Unit tests won't cover the cache backend until the 8.x series.
|
||||
|
||||
This won't be fixed, by design. Drupal 8.x now ships a complete cache backend
|
||||
unit tests suite which will be used when this module will be upgraded.
|
||||
|
||||
7.x testing
|
||||
===========
|
||||
|
||||
7.x testing will keep it to the bare minimum and will test some minor bugs such
|
||||
as admin UI or autoloading related bugs.
|
||||
|
||||
Cleanup environment
|
||||
===================
|
||||
|
||||
php -f scripts/run-tests.sh -- --clean
|
||||
|
||||
Run common tests
|
||||
================
|
||||
|
||||
|
||||
php -f scripts/run-tests.sh -- --verbose --color \
|
||||
--url "http://yoursite" \
|
||||
--class "Redis_Tests_Client_UnitTestCase"
|
||||
|
||||
Run all PhpRedis tests
|
||||
======================
|
||||
|
||||
php -f scripts/run-tests.sh -- --verbose --color \
|
||||
--url "http://laborange.net" \
|
||||
--class "Redis_Tests_Cache_PhpRedisFixesUnitTestCase,Redis_Tests_Cache_PhpRedisFlushUnitTestCase,Redis_Tests_Cache_PhpRedisShardedFixesUnitTestCase,Redis_Tests_Cache_PhpRedisShardedFlushUnitTestCase,Redis_Tests_Cache_PhpRedisShardedWithPipelineFixesUnitTestCase,Redis_Tests_Lock_PhpRedisLockingUnitTestCase,Redis_Tests_Path_PhpRedisPathUnitTestCase,Redis_Tests_Queue_PhpRedisQueueUnitTestCase"
|
||||
|
||||
Run all Predis tests
|
||||
======================
|
||||
|
||||
php -f scripts/run-tests.sh -- --verbose --color \
|
||||
--url "http://yoursite" \
|
||||
--class "Redis_Tests_Cache_PredisFixesUnitTestCase,Redis_Tests_Cache_PredisFlushUnitTestCase,Redis_Tests_Cache_PredisShardedFixesUnitTestCase,Redis_Tests_Cache_PredisShardedFlushUnitTestCase,Redis_Tests_Cache_PredisShardedWithPipelineFixesUnitTestCase,Redis_Tests_Lock_PredisLockingUnitTestCase,Redis_Tests_Path_PredisPathUnitTestCase"
|
474
sites/all/modules/contrib/dev/redis/README.txt
Normal file
474
sites/all/modules/contrib/dev/redis/README.txt
Normal file
@ -0,0 +1,474 @@
|
||||
Redis cache backends
|
||||
====================
|
||||
|
||||
This package provides two different Redis cache backends. If you want to use
|
||||
Redis as cache backend, you have to choose one of the two, but you cannot use
|
||||
both at the same time. Well, it will be technically possible, but it would be
|
||||
quite a dumb thing to do.
|
||||
|
||||
Predis
|
||||
------
|
||||
|
||||
This implementation uses the Predis PHP library. It is compatible PHP 5.3
|
||||
only.
|
||||
|
||||
PhpRedis
|
||||
--------
|
||||
|
||||
This implementation uses the PhpRedis PHP extention. In order to use it, you
|
||||
probably will need to compile the extension yourself.
|
||||
|
||||
Redis version
|
||||
-------------
|
||||
|
||||
This module requires Redis version to be 2.6.0 or later with LUA scrpting
|
||||
enabled due to the EVAL command usage.
|
||||
|
||||
If you can't upgrade you Redis server:
|
||||
|
||||
- 3.x release will only officially support Redis server <= 2.8 and 3.x
|
||||
nevertheless you may use it with Redis 2.4 if you configure your cache
|
||||
backend to operate in sharding mode.
|
||||
|
||||
- For Redis 2.4 use the latest 2.x release of this module or use the
|
||||
3.x release with sharding mode enabled.
|
||||
|
||||
- For Redis <=2.3 use any version of this module <=2.6
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
Both backends provide the exact same functionalities. The major difference is
|
||||
because PhpRedis uses a PHP extension, and not PHP code, it will performe a
|
||||
lot better (Predis needs PHP userland code to be loaded).
|
||||
|
||||
Difference is not that visible, it's really a few millisec on my testing box,
|
||||
in case you attempt to profile the code, traces will be a lot bigger.
|
||||
|
||||
Note that most of the settings are shared. See next sections.
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Quick setup
|
||||
-----------
|
||||
|
||||
Here is a simple yet working easy way to setup the module.
|
||||
This method will allow Drupal to use Redis for all caches and locks
|
||||
and path alias cache replacement.
|
||||
|
||||
$conf['redis_client_interface'] = 'PhpRedis'; // Can be "Predis".
|
||||
$conf['redis_client_host'] = '1.2.3.4'; // Your Redis instance hostname.
|
||||
$conf['lock_inc'] = 'sites/all/modules/redis/redis.lock.inc';
|
||||
$conf['path_inc'] = 'sites/all/modules/redis/redis.path.inc';
|
||||
$conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc';
|
||||
$conf['cache_default_class'] = 'Redis_Cache';
|
||||
|
||||
See next chapters for more information.
|
||||
|
||||
Is there any cache bins that should *never* go into Redis?
|
||||
----------------------------------------------------------
|
||||
|
||||
TL;DR: No. Except for 'cache_form' if you use Redis with LRU eviction.
|
||||
|
||||
Redis has been maturing a lot over time, and will apply different sensible
|
||||
settings for different bins; It's today very stable.
|
||||
|
||||
Advanced configuration
|
||||
======================
|
||||
|
||||
Use the compressed cache
|
||||
------------------------
|
||||
|
||||
Please note this is for now an experimental feature. As a personnal note
|
||||
from the module author, it should be safe to use.
|
||||
|
||||
Use this cache class setting to enable compression. This will save usually
|
||||
about 80% RAM at the cost of some milliseconds server time.
|
||||
|
||||
$conf['cache_default_class'] = 'Redis_CacheCompressed';
|
||||
|
||||
Additionnaly, you can alter the default size compression threshold, under which
|
||||
entries will not be compressed (size is in bytes, set 0 to always compress):
|
||||
|
||||
$conf['cache_compression_size_threshold'] = 100;
|
||||
|
||||
You can also change the compression level, which an positive integer between
|
||||
1 and 9, 1 being the lowest but fastest compression ratio, 9 being the most
|
||||
aggressive compression but is a lot slower. From testing, setting it to the
|
||||
lower level (1) gives 80% memory usage decrease, which is more than enough.
|
||||
|
||||
$conf['cache_compression_ratio'] = 5;
|
||||
|
||||
Please note that those settings are global and not on a cache bin basis, you can
|
||||
already control whenever the compression is to be used or not by selecting a
|
||||
different cache class on per cache bin basis.
|
||||
|
||||
If you switch from the standard default backend (without compression) to the
|
||||
compressed cache backend, it will recover transparently uncompressed data and
|
||||
proceed normally without additional cache eviction, it safe to upgrade.
|
||||
Donwgrading from compressed data to uncompressed data won't work, but the
|
||||
cache backend will just give you cache hit miss and it will work seamlessly
|
||||
too without any danger for the site.
|
||||
|
||||
Choose the Redis client library to use
|
||||
--------------------------------------
|
||||
|
||||
Add into your settings.php file:
|
||||
|
||||
$conf['redis_client_interface'] = 'PhpRedis';
|
||||
|
||||
You can replace 'PhpRedis' with 'Predis', depending on the library you chose.
|
||||
|
||||
Note that this is optional but recommended. If you don't set this variable the
|
||||
module will proceed to class lookups and attempt to choose the best client
|
||||
available, having always a preference for the Predis one.
|
||||
|
||||
Tell Drupal to use the cache backend
|
||||
------------------------------------
|
||||
|
||||
Usual cache backend configuration, as follows, to add into your settings.php
|
||||
file like any other backend:
|
||||
|
||||
$conf['cache_backends'][] = 'sites/all/modules/redis/redis.autoload.inc';
|
||||
$conf['cache_class_cache'] = 'Redis_Cache';
|
||||
$conf['cache_class_cache_menu'] = 'Redis_Cache';
|
||||
$conf['cache_class_cache_bootstrap'] = 'Redis_Cache';
|
||||
// ... Any other bins.
|
||||
|
||||
Tell Drupal to use the lock backend
|
||||
-----------------------------------
|
||||
|
||||
Usual lock backend override, update you settings.php file as this:
|
||||
|
||||
$conf['lock_inc'] = 'sites/all/modules/redis/redis.lock.inc';
|
||||
|
||||
Tell Drupal to use the path alias backend
|
||||
-----------------------------------------
|
||||
|
||||
Usual path backend override, update you settings.php file as this:
|
||||
|
||||
$conf['path_inc'] = 'sites/all/modules/redis/redis.path.inc';
|
||||
|
||||
Notice that there is an additional variable for path handling that is set
|
||||
per default which will ignore any path that is an admin path, gaining a few
|
||||
SQL queries. If you want to be able to set aliases on admin path and restore
|
||||
an almost default Drupal core behavior, you should add this line into your
|
||||
settings.php file:
|
||||
|
||||
$conf['path_alias_admin_blacklist'] = FALSE;
|
||||
|
||||
Drupal 6 and lock backend
|
||||
-------------------------
|
||||
|
||||
Considering this is a Drupal 7 module only downloading it in Drupal 6 will make
|
||||
the module UI telling you this module is unsupported yet you can use the lock
|
||||
backend on Drupal 6.
|
||||
|
||||
Read your Drupal 6 core documentation and use the redis.lock.inc file as
|
||||
lock_inc replacement the same way its being done for Drupal 7 and it should
|
||||
work. Note that this is untested by the module maintainer (feedback will be
|
||||
greatly appreciated).
|
||||
|
||||
Common settings
|
||||
===============
|
||||
|
||||
Connect throught a UNIX socket
|
||||
------------------------------
|
||||
|
||||
All you have to do is specify this line:
|
||||
|
||||
$conf['redis_client_socket'] = '/some/path/redis.sock';
|
||||
|
||||
Both drivers support it.
|
||||
|
||||
Connect to a remote host
|
||||
------------------------
|
||||
|
||||
If your Redis instance is remote, you can use this syntax:
|
||||
|
||||
$conf['redis_client_host'] = '1.2.3.4';
|
||||
$conf['redis_client_port'] = 1234;
|
||||
|
||||
Port is optional, default is 6379 (default Redis port).
|
||||
|
||||
Using a specific database
|
||||
-------------------------
|
||||
|
||||
Per default, Redis ships the database "0". All default connections will be use
|
||||
this one if nothing is specified.
|
||||
|
||||
Depending on you OS or OS distribution, you might have numerous database. To
|
||||
use one in particular, just add to your settings.php file:
|
||||
|
||||
$conf['redis_client_base'] = 12;
|
||||
|
||||
Please note that if you are working in shard mode, you should never set this
|
||||
variable.
|
||||
|
||||
Connection to a password protected instance
|
||||
-------------------------------------------
|
||||
|
||||
If you are using a password protected instance, specify the password this way:
|
||||
|
||||
$conf['redis_client_password'] = "mypassword";
|
||||
|
||||
Depending on the backend, using a wrong auth will behave differently:
|
||||
|
||||
- Predis will throw an exception and make Drupal fail during early boostrap.
|
||||
|
||||
- PhpRedis will make Redis calls silent and creates some PHP warnings, thus
|
||||
Drupal will behave as if it was running with a null cache backend (no cache
|
||||
at all).
|
||||
|
||||
Prefixing site cache entries (avoiding sites name collision)
|
||||
------------------------------------------------------------
|
||||
|
||||
If you need to differenciate multiple sites using the same Redis instance and
|
||||
database, you will need to specify a prefix for your site cache entries.
|
||||
|
||||
Important note: most people don't need that feature since that when no prefix
|
||||
is specified, the Redis module will attempt to use the a hash of the database
|
||||
credentials in order to provide a multisite safe default behavior. This means
|
||||
that the module will also safely work in CLI scripts.
|
||||
|
||||
Cache prefix configuration attemps to use a unified variable accross contrib
|
||||
backends that support this feature. This variable name is 'cache_prefix'.
|
||||
|
||||
This variable is polymorphic, the simplest version is to provide a raw string
|
||||
that will be the default prefix for all cache bins:
|
||||
|
||||
$conf['cache_prefix'] = 'mysite_';
|
||||
|
||||
Alternatively, to provide the same functionality, you can provide the variable
|
||||
as an array:
|
||||
|
||||
$conf['cache_prefix']['default'] = 'mysite_';
|
||||
|
||||
This allows you to provide different prefix depending on the bin name. Common
|
||||
usage is that each key inside the 'cache_prefix' array is a bin name, the value
|
||||
the associated prefix. If the value is explicitely FALSE, then no prefix is
|
||||
used for this bin.
|
||||
|
||||
The 'default' meta bin name is provided to define the default prefix for non
|
||||
specified bins. It behaves like the other names, which means that an explicit
|
||||
FALSE will order the backend not to provide any prefix for any non specified
|
||||
bin.
|
||||
|
||||
Here is a complex sample:
|
||||
|
||||
// Default behavior for all bins, prefix is 'mysite_'.
|
||||
$conf['cache_prefix']['default'] = 'mysite_';
|
||||
|
||||
// Set no prefix explicitely for 'cache' and 'cache_bootstrap' bins.
|
||||
$conf['cache_prefix']['cache'] = FALSE;
|
||||
$conf['cache_prefix']['cache_bootstrap'] = FALSE;
|
||||
|
||||
// Set another prefix for 'cache_menu' bin.
|
||||
$conf['cache_prefix']['cache_menu'] = 'menumysite_';
|
||||
|
||||
Note that this last notice is Redis only specific, because per default Redis
|
||||
server will not namespace data, thus sharing an instance for multiple sites
|
||||
will create conflicts. This is not true for every contributed backends.
|
||||
|
||||
Sharding vs normal mode
|
||||
-----------------------
|
||||
|
||||
Per default the Redis cache backend will be in "normal" mode, meaning that
|
||||
every flush call will trigger and EVAL lua script that will proceed to cache
|
||||
wipeout and cleanup the Redis database from stalled entries.
|
||||
|
||||
Nevertheless, if you are working with a Redis server < 2.6 or in a sharded
|
||||
environment, you cannot multiple keys per command nor proceed to EVAL'ed
|
||||
scripts, you will then need to switch to the sharded mode.
|
||||
|
||||
Sharded mode will never delete entries on flush calls, but register a key
|
||||
with the current flush time instead. Cache entries will then be deleted on
|
||||
read if the entry checksum does not match or is older than the latest flush
|
||||
call. Note that this mode is fast and safe, but must be used accordingly
|
||||
with the default lifetime for permanent items, else your Redis server might
|
||||
keep stalled entries into its database forever.
|
||||
|
||||
In order to enable the sharded mode, set into your settings.php file:
|
||||
|
||||
$conf['redis_flush_mode'] = 3;
|
||||
|
||||
Please note that the value 3 is there to keep backward compatibility with
|
||||
older versions of the Redis module and will not change.
|
||||
|
||||
Note that previous Redis module version allowed to set a per-bin setting for
|
||||
the clear mode value; nevertheless the clear mode is not a valid setting
|
||||
anymore and the past issues have been resolved. Only the global value will
|
||||
work as of now.
|
||||
|
||||
Sharding and pipelining
|
||||
-----------------------
|
||||
|
||||
Whe using this module with sharding mode you may have a sharding proxy able to
|
||||
do command pipelining. If that is the case, you should switch to "sharding with
|
||||
pipelining" mode instead:
|
||||
|
||||
$conf['redis_flush_mode'] = 4;
|
||||
|
||||
Note that if you use the sharding mode because you use an older version of the
|
||||
Redis server, you should always use this mode to ensure the best performances.
|
||||
|
||||
Default lifetime for permanent items
|
||||
------------------------------------
|
||||
|
||||
Redis when reaching its maximum memory limit will stop writing data in its
|
||||
storage engine: this is a feature that avoid the Redis server crashing when
|
||||
there is no memory left on the machine.
|
||||
|
||||
As a workaround, Redis can be configured as a LRU cache for both volatile or
|
||||
permanent items, which means it can behave like Memcache; Problem is that if
|
||||
you use Redis as a permanent storage for other business matters than this
|
||||
module you cannot possibly configure it to drop permanent items or you'll
|
||||
loose data.
|
||||
|
||||
This workaround allows you to explicity set a very long or configured default
|
||||
lifetime for CACHE_PERMANENT items (that would normally be permanent) which
|
||||
will mark them as being volatile in Redis storage engine: this then allows you
|
||||
to configure a LRU behavior for volatile keys without engaging the permenent
|
||||
business stuff in a dangerous LRU mechanism; Cache items even if permament will
|
||||
be dropped when unused using this.
|
||||
|
||||
Per default the TTL for permanent items will set to safe-enough value which is
|
||||
one year; No matter how Redis will be configured default configuration or lazy
|
||||
admin will inherit from a safe module behavior with zero-conf.
|
||||
|
||||
For advanturous people, you can manage the TTL on a per bin basis and change
|
||||
the default one:
|
||||
|
||||
// Make CACHE_PERMANENT items being permanent once again
|
||||
// 0 is a special value usable for all bins to explicitely tell the
|
||||
// cache items will not be volatile in Redis.
|
||||
$conf['redis_perm_ttl'] = 0;
|
||||
|
||||
// Make them being volatile with a default lifetime of 1 year.
|
||||
$conf['redis_perm_ttl'] = "1 year";
|
||||
|
||||
// You can override on a per-bin basis;
|
||||
// For example make cached field values live only 3 monthes:
|
||||
$conf['redis_perm_ttl_cache_field'] = "3 months";
|
||||
|
||||
// But you can also put a timestamp in there; In this case the
|
||||
// value must be a STRICTLY TYPED integer:
|
||||
$conf['redis_perm_ttl_cache_field'] = 2592000; // 30 days.
|
||||
|
||||
Time interval string will be parsed using DateInterval::createFromDateString
|
||||
please refer to its documentation:
|
||||
|
||||
http://www.php.net/manual/en/dateinterval.createfromdatestring.php
|
||||
|
||||
Please also be careful about the fact that those settings are overriden by
|
||||
the 'cache_lifetime' Drupal variable, which should always be set to 0.
|
||||
Moreover, this setting will affect all cache entries without exception so
|
||||
be careful and never set values too low if you don't want this setting to
|
||||
override default expire value given by modules on temporary cache entries.
|
||||
|
||||
Lock backends
|
||||
-------------
|
||||
|
||||
Both implementations provides a Redis lock backend. Redis lock backend proved to
|
||||
be faster than the default SQL based one when using both servers on the same box.
|
||||
|
||||
Both backends, thanks to the Redis WATCH, MULTI and EXEC commands provides a
|
||||
real race condition free mutexes by using Redis transactions.
|
||||
|
||||
Queue backend
|
||||
-------------
|
||||
|
||||
This module provides an experimental queue backend. It is for now implemented
|
||||
only using the PhpRedis driver, any attempt to use it using Predis will result
|
||||
in runtime errors.
|
||||
|
||||
If you want to change the queue driver system wide, set this into your
|
||||
setting.php file:
|
||||
|
||||
$conf['queue_default_class'] = 'Redis_Queue';
|
||||
$conf['queue_default_reliable_class'] = 'Redis_Queue';
|
||||
|
||||
Note that some queue implementations such as the batch queue are hardcoded
|
||||
within Drupal and will always use a database dependent implementation.
|
||||
|
||||
If you need to proceed with finer tuning, you can set a per-queue class in
|
||||
such way:
|
||||
|
||||
$conf['queue_class_NAME'] = 'Redis_Queue';
|
||||
|
||||
Where NAME is the arbitrary module given queue name, used as first parameter
|
||||
for the method DrupalQueue::get().
|
||||
|
||||
THIS IS STILL VERY EXPERIMENTAL. The queue should work without any problems
|
||||
except it does not implement the item lease time correctly, this means that
|
||||
items that are too long to process won't be released back and forth but will
|
||||
block the thread processing it instead. This is the only side effect I am
|
||||
aware of at the current time.
|
||||
|
||||
Failover, sharding and partionning
|
||||
==================================
|
||||
|
||||
Important notice
|
||||
----------------
|
||||
|
||||
There are numerous support and feature request issues about client sharding,
|
||||
failover ability, multi-server connection, ability to read from slave and
|
||||
server clustering opened in the issue queue. Note that there is not one
|
||||
universally efficient solution for this: most of the solutions require that
|
||||
you cannot use the MULTI/EXEC command using more than one key, and that you
|
||||
cannot use complex UNION and intersection features anymore.
|
||||
|
||||
This module does not implement any kind of client side key hashing or sharding
|
||||
and never intended to; We recommend that you read the official Redis
|
||||
documentation page about partionning.
|
||||
|
||||
The best solution for clustering and sharding today seems to be the proxy
|
||||
assisted partionning using tools such as Twemproxy.
|
||||
|
||||
Current components state
|
||||
------------------------
|
||||
|
||||
As of now, provided components are simple enough so they never use WATCH or
|
||||
MULTI/EXEC transaction blocks on multiple keys : this means that you can use
|
||||
them in an environment doing data sharding/partionning. This remains true
|
||||
except when you use a proxy that blocks those commands such as Twemproxy.
|
||||
|
||||
Lock
|
||||
----
|
||||
|
||||
Lock backend works on a single key per lock, it theorically guarantees the
|
||||
atomicity of operations therefore is usable in a sharded environement. Sadly
|
||||
if you use proxy assisted sharding such as Twemproxy, WATCH, MULTI and EXEC
|
||||
commands won't pass making it non shardable.
|
||||
|
||||
Path
|
||||
----
|
||||
|
||||
Path backend does not use on transactions, it is safe to use in a sharded
|
||||
environment. Note that this backend uses a single HASH key per language
|
||||
and per way (alias to source or source to alias) and therefore won't benefit
|
||||
greatly if not at all from being sharded.
|
||||
|
||||
Cache
|
||||
-----
|
||||
|
||||
Cache uses pipelined transactions but does not uses it to guarantee any kind
|
||||
of data consistency. If you use a smart sharding proxy it is supposed to work
|
||||
transparently without any hickups.
|
||||
|
||||
Queue
|
||||
-----
|
||||
|
||||
Queue is still in development. There might be problems in the long term for
|
||||
this component in sharded environments.
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
Due to Drupal unit testing API being incredibly stupid, the unit tests can only
|
||||
work with PHP >=5.3 while the module will work gracefully with PHP 5.2 (at least
|
||||
using the PhpRedis client).
|
||||
|
||||
I did not find any hint about making tests being configurable, so per default
|
||||
the tested Redis server must always be on localhost with default configuration.
|
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_AbstractBackend implements Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Key components name separator
|
||||
*/
|
||||
const KEY_SEPARATOR = ':';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $client;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param mixed $client
|
||||
* Redis client
|
||||
* @param string $namespace
|
||||
* Component namespace
|
||||
* @param string $prefix
|
||||
* Component prefix
|
||||
*/
|
||||
public function __construct($client, $namespace = null, $prefix = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->prefix = $prefix;
|
||||
|
||||
if (null !== $namespace) {
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
final public function setClient($client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
final public function getClient()
|
||||
{
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
final public function setPrefix($prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
final public function getPrefix()
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
final public function setNamespace($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
final public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prefixed key
|
||||
*
|
||||
* @param string|string[] $parts
|
||||
* Arbitrary number of strings to compose the key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey($parts = array())
|
||||
{
|
||||
$key = array();
|
||||
|
||||
if (null !== $this->prefix) {
|
||||
$key[] = $this->prefix;
|
||||
}
|
||||
if (null !== $this->namespace) {
|
||||
$key[] = $this->namespace;
|
||||
}
|
||||
|
||||
if ($parts) {
|
||||
if (is_array($parts)) {
|
||||
foreach ($parts as $part) {
|
||||
if ($part) {
|
||||
$key[] = $part;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$key[] = $parts;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(self::KEY_SEPARATOR, array_filter($key));
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client based Redis component
|
||||
*/
|
||||
interface Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Set client
|
||||
*
|
||||
* @param mixed $client
|
||||
*/
|
||||
public function setClient($client);
|
||||
|
||||
/**
|
||||
* Get client
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getClient();
|
||||
|
||||
/**
|
||||
* Set prefix
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function setPrefix($prefix);
|
||||
|
||||
/**
|
||||
* Get prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPrefix();
|
||||
|
||||
/**
|
||||
* Set namespace
|
||||
*
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function setNamespace($namespace);
|
||||
|
||||
/**
|
||||
* Get namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace();
|
||||
|
||||
/**
|
||||
* Get full key name using the set prefix
|
||||
*
|
||||
* @param string ...
|
||||
* Any numer of strings to append to path using the separator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey();
|
||||
}
|
655
sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php
Normal file
655
sites/all/modules/contrib/dev/redis/lib/Redis/Cache.php
Normal file
@ -0,0 +1,655 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Because those objects will be spawned during boostrap all its configuration
|
||||
* must be set in the settings.php file.
|
||||
*
|
||||
* You will find the driver specific implementation in the Redis_Cache_*
|
||||
* classes as they may differ in how the API handles transaction, pipelining
|
||||
* and return values.
|
||||
*/
|
||||
class Redis_Cache
|
||||
implements DrupalCacheInterface
|
||||
{
|
||||
/**
|
||||
* Default lifetime for permanent items.
|
||||
* Approximatively 1 year.
|
||||
*/
|
||||
const LIFETIME_PERM_DEFAULT = 31536000;
|
||||
|
||||
/**
|
||||
* Uses EVAL scripts to flush data when called
|
||||
*
|
||||
* This remains the default behavior and is safe until you use a single
|
||||
* Redis server instance and its version is >= 2.6 (older version don't
|
||||
* support EVAL).
|
||||
*/
|
||||
const FLUSH_NORMAL = 0;
|
||||
|
||||
/**
|
||||
* This mode is tailored for sharded Redis servers instances usage: it
|
||||
* will never delete entries but only mark the latest flush timestamp
|
||||
* into one of the servers in the shard. It will proceed to delete on
|
||||
* read single entries when invalid entries are being loaded.
|
||||
*/
|
||||
const FLUSH_SHARD = 3;
|
||||
|
||||
/**
|
||||
* Same as the one above, plus attempt to do pipelining when possible.
|
||||
*
|
||||
* This is supposed to work with sharding proxies that supports
|
||||
* pipelining themselves, such as Twemproxy.
|
||||
*/
|
||||
const FLUSH_SHARD_WITH_PIPELINING = 4;
|
||||
|
||||
/**
|
||||
* Computed keys are let's say arround 60 characters length due to
|
||||
* key prefixing, which makes 1,000 keys DEL command to be something
|
||||
* arround 50,000 bytes length: this is huge and may not pass into
|
||||
* Redis, let's split this off.
|
||||
* Some recommend to never get higher than 1,500 bytes within the same
|
||||
* command which makes us forced to split this at a very low threshold:
|
||||
* 20 seems a safe value here (1,280 average length).
|
||||
*/
|
||||
const KEY_THRESHOLD = 20;
|
||||
|
||||
/**
|
||||
* @var Redis_Cache_BackendInterface
|
||||
*/
|
||||
protected $backend;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bin;
|
||||
|
||||
/**
|
||||
* When the global 'cache_lifetime' Drupal variable is set to a value, the
|
||||
* cache backends should not expire temporary entries by themselves per
|
||||
* Drupal signature. Volatile items will be dropped accordingly to their
|
||||
* set lifetime.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $allowTemporaryFlush = true;
|
||||
|
||||
/**
|
||||
* When in shard mode, the backend cannot proceed to multiple keys
|
||||
* operations, and won't delete keys on flush calls.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $isSharded = false;
|
||||
|
||||
/**
|
||||
* When in shard mode, the proxy may or may not support pipelining,
|
||||
* Twemproxy is known to support it.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $allowPipeline = false;
|
||||
|
||||
/**
|
||||
* Default TTL for CACHE_PERMANENT items.
|
||||
*
|
||||
* See "Default lifetime for permanent items" section of README.txt
|
||||
* file for a comprehensive explaination of why this exists.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $permTtl = self::LIFETIME_PERM_DEFAULT;
|
||||
|
||||
/**
|
||||
* Maximum TTL for this bin from Drupal configuration.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $maxTtl = 0;
|
||||
|
||||
/**
|
||||
* Flush permanent and volatile cached values
|
||||
*
|
||||
* @var string[]
|
||||
* First value is permanent latest flush time and second value
|
||||
* is volatile latest flush time
|
||||
*/
|
||||
protected $flushCache = null;
|
||||
|
||||
/**
|
||||
* Is this bin in shard mode
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSharded()
|
||||
{
|
||||
return $this->isSharded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this bin allow pipelining through sharded environment
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function allowPipeline()
|
||||
{
|
||||
return $this->allowPipeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this bin allow temporary item flush
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function allowTemporaryFlush()
|
||||
{
|
||||
return $this->allowTemporaryFlush;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TTL for CACHE_PERMANENT items.
|
||||
*
|
||||
* @return int
|
||||
* Lifetime in seconds.
|
||||
*/
|
||||
public function getPermTtl()
|
||||
{
|
||||
return $this->permTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum TTL for all items.
|
||||
*
|
||||
* @return int
|
||||
* Lifetime in seconds.
|
||||
*/
|
||||
public function getMaxTtl()
|
||||
{
|
||||
return $this->maxTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($bin)
|
||||
{
|
||||
$this->bin = $bin;
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_CACHE);
|
||||
$this->backend = new $className(Redis_Client::getClient(), $bin, Redis_Client::getDefaultPrefix($bin));
|
||||
|
||||
$this->refreshCapabilities();
|
||||
$this->refreshPermTtl();
|
||||
$this->refreshMaxTtl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the clear mode.
|
||||
*/
|
||||
public function refreshCapabilities()
|
||||
{
|
||||
if (0 < variable_get('cache_lifetime', 0)) {
|
||||
// Per Drupal default behavior, when the 'cache_lifetime' variable
|
||||
// is set we must not flush any temporary items since they have a
|
||||
// life time.
|
||||
$this->allowTemporaryFlush = false;
|
||||
}
|
||||
|
||||
if (null !== ($mode = variable_get('redis_flush_mode', null))) {
|
||||
$mode = (int)$mode;
|
||||
} else {
|
||||
$mode = self::FLUSH_NORMAL;
|
||||
}
|
||||
|
||||
$this->isSharded = self::FLUSH_SHARD === $mode || self::FLUSH_SHARD_WITH_PIPELINING === $mode;
|
||||
$this->allowPipeline = self::FLUSH_SHARD !== $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the right permanent items TTL.
|
||||
*/
|
||||
protected function refreshPermTtl()
|
||||
{
|
||||
$ttl = null;
|
||||
if (null === ($ttl = variable_get('redis_perm_ttl_' . $this->bin, null))) {
|
||||
if (null === ($ttl = variable_get('redis_perm_ttl', null))) {
|
||||
$ttl = self::LIFETIME_PERM_DEFAULT;
|
||||
}
|
||||
}
|
||||
if ($ttl === (int)$ttl) {
|
||||
$this->permTtl = $ttl;
|
||||
} else {
|
||||
if ($iv = DateInterval::createFromDateString($ttl)) {
|
||||
// http://stackoverflow.com/questions/14277611/convert-dateinterval-object-to-seconds-in-php
|
||||
$this->permTtl = ($iv->y * 31536000 + $iv->m * 2592000 + $iv->d * 86400 + $iv->h * 3600 + $iv->i * 60 + $iv->s);
|
||||
} else {
|
||||
// Sorry but we have to log this somehow.
|
||||
trigger_error(sprintf("Parsed TTL '%s' has an invalid value: switching to default", $ttl));
|
||||
$this->permTtl = self::LIFETIME_PERM_DEFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find from Drupal variables the maximum cache lifetime.
|
||||
*/
|
||||
public function refreshMaxTtl()
|
||||
{
|
||||
// And now cache lifetime. Be aware we exclude negative values
|
||||
// considering those are Drupal misconfiguration.
|
||||
$maxTtl = variable_get('cache_lifetime', 0);
|
||||
if (0 < $maxTtl) {
|
||||
if ($maxTtl < $this->permTtl) {
|
||||
$this->maxTtl = $maxTtl;
|
||||
} else {
|
||||
$this->maxTtl = $this->permTtl;
|
||||
}
|
||||
} else if ($this->permTtl) {
|
||||
$this->maxTtl = $this->permTtl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set last flush time
|
||||
*
|
||||
* @param string $permanent
|
||||
* @param string $volatile
|
||||
*/
|
||||
public function setLastFlushTime($permanent = false, $volatile = false)
|
||||
{
|
||||
// Here we need to fetch absolute values from backend, to avoid
|
||||
// concurrency problems and ensure data validity.
|
||||
list($flushPerm, $flushVolatile) = $this->backend->getLastFlushTime();
|
||||
|
||||
$checksum = $this->getValidChecksum(
|
||||
max(array(
|
||||
$flushPerm,
|
||||
$flushVolatile,
|
||||
$permanent,
|
||||
time(),
|
||||
))
|
||||
);
|
||||
|
||||
if ($permanent) {
|
||||
$this->backend->setLastFlushTimeFor($checksum, false);
|
||||
$this->backend->setLastFlushTimeFor($checksum, true);
|
||||
$this->flushCache = array($checksum, $checksum);
|
||||
} else if ($volatile) {
|
||||
$this->backend->setLastFlushTimeFor($checksum, true);
|
||||
$this->flushCache = array($flushPerm, $checksum);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest flush time
|
||||
*
|
||||
* @return string[]
|
||||
* First value is the latest flush time for permanent entries checksum,
|
||||
* second value is the latest flush time for volatile entries checksum.
|
||||
*/
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
if (!$this->flushCache) {
|
||||
$this->flushCache = $this->backend->getLastFlushTime();
|
||||
}
|
||||
|
||||
// At the very first hit, we might not have the timestamps set, thus
|
||||
// we need to create them to avoid our entry being considered as
|
||||
// invalid
|
||||
if (!$this->flushCache[0]) {
|
||||
$this->setLastFlushTime(true, true);
|
||||
} else if (!$this->flushCache[1]) {
|
||||
$this->setLastFlushTime(false, true);
|
||||
}
|
||||
|
||||
return $this->flushCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cache entry
|
||||
*
|
||||
* @param string $cid
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
if (CACHE_TEMPORARY === $expire) {
|
||||
$validityThreshold = max(array($flushVolatile, $flushPerm));
|
||||
} else {
|
||||
$validityThreshold = $flushPerm;
|
||||
}
|
||||
|
||||
$time = $this->getValidChecksum($validityThreshold);
|
||||
|
||||
$hash = array(
|
||||
'cid' => $cid,
|
||||
'created' => $time,
|
||||
'expire' => $expire,
|
||||
);
|
||||
|
||||
// Let Redis handle the data types itself.
|
||||
if (!is_string($data)) {
|
||||
$hash['data'] = serialize($data);
|
||||
$hash['serialized'] = 1;
|
||||
} else {
|
||||
$hash['data'] = $data;
|
||||
$hash['serialized'] = 0;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand cache entry from fetched data
|
||||
*
|
||||
* @param array $values
|
||||
* Raw values fetched from Redis server data
|
||||
*
|
||||
* @return array
|
||||
* Or FALSE if entry is invalid
|
||||
*/
|
||||
protected function expandEntry(array $values, $flushPerm, $flushVolatile)
|
||||
{
|
||||
// Check for entry being valid.
|
||||
if (empty($values['cid'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This ensures backward compatibility with older version of
|
||||
// this module's data still stored in Redis.
|
||||
if (isset($values['expire'])) {
|
||||
$expire = (int)$values['expire'];
|
||||
// Ensure the entry is valid and have not expired.
|
||||
if ($expire !== CACHE_PERMANENT && $expire !== CACHE_TEMPORARY && $expire <= time()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the entry does not predate the last flush time.
|
||||
if ($this->allowTemporaryFlush && !empty($values['volatile'])) {
|
||||
$validityThreshold = max(array($flushPerm, $flushVolatile));
|
||||
} else {
|
||||
$validityThreshold = $flushPerm;
|
||||
}
|
||||
|
||||
if ($values['created'] <= $validityThreshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entry = (object)$values;
|
||||
|
||||
// Reduce the checksum to the real timestamp part
|
||||
$entry->created = (int)$entry->created;
|
||||
|
||||
if ($entry->serialized) {
|
||||
$entry->data = unserialize($entry->data);
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($cid)
|
||||
{
|
||||
$values = $this->backend->get($cid);
|
||||
|
||||
if (empty($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
$entry = $this->expandEntry($values, $flushPerm, $flushVolatile);
|
||||
|
||||
if (!$entry) { // This entry exists but is invalid.
|
||||
$this->backend->delete($cid);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(&$cids)
|
||||
{
|
||||
$ret = array();
|
||||
$delete = array();
|
||||
|
||||
if (!$this->allowPipeline) {
|
||||
$entries = array();
|
||||
foreach ($cids as $cid) {
|
||||
if ($entry = $this->backend->get($cid)) {
|
||||
$entries[$cid] = $entry;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$entries = $this->backend->getMultiple($cids);
|
||||
}
|
||||
|
||||
list($flushPerm, $flushVolatile) = $this->getLastFlushTime();
|
||||
|
||||
foreach ($cids as $key => $cid) {
|
||||
if (!empty($entries[$cid])) {
|
||||
$entry = $this->expandEntry($entries[$cid], $flushPerm, $flushVolatile);
|
||||
} else {
|
||||
$entry = null;
|
||||
}
|
||||
if (empty($entry)) {
|
||||
$delete[] = $cid;
|
||||
} else {
|
||||
$ret[$cid] = $entry;
|
||||
unset($cids[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($delete)) {
|
||||
if ($this->allowPipeline) {
|
||||
foreach ($delete as $id) {
|
||||
$this->backend->delete($id);
|
||||
}
|
||||
} else {
|
||||
$this->backend->deleteMultiple($delete);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
$hash = $this->createEntryHash($cid, $data, $expire);
|
||||
$maxTtl = $this->getMaxTtl();
|
||||
|
||||
switch ($expire) {
|
||||
|
||||
case CACHE_PERMANENT:
|
||||
$this->backend->set($cid, $hash, $maxTtl, false);
|
||||
break;
|
||||
|
||||
case CACHE_TEMPORARY:
|
||||
$this->backend->set($cid, $hash, $maxTtl, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
$ttl = $expire - time();
|
||||
// Ensure $expire consistency
|
||||
if ($ttl <= 0) {
|
||||
// Entry has already expired, but we may have a stalled
|
||||
// older cache entry remaining there, ensure it wont
|
||||
// happen by doing a preventive delete
|
||||
$this->backend->delete($cid);
|
||||
} else {
|
||||
if ($maxTtl && $maxTtl < $ttl) {
|
||||
$ttl = $maxTtl;
|
||||
}
|
||||
$this->backend->set($cid, $hash, $ttl, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear($cid = null, $wildcard = false)
|
||||
{
|
||||
if (null === $cid && !$wildcard) {
|
||||
// Drupal asked for volatile entries flush, this will happen
|
||||
// during cron run, mostly
|
||||
$this->setLastFlushTime(false, true);
|
||||
|
||||
if (!$this->isSharded && $this->allowTemporaryFlush) {
|
||||
$this->backend->flushVolatile();
|
||||
}
|
||||
} else if ($wildcard) {
|
||||
if (empty($cid)) {
|
||||
// This seems to be an error, just do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
if ('*' === $cid) {
|
||||
// Use max() to ensure we invalidate both correctly
|
||||
$this->setLastFlushTime(true);
|
||||
|
||||
if (!$this->isSharded) {
|
||||
$this->backend->flush();
|
||||
}
|
||||
} else {
|
||||
if (!$this->isSharded) {
|
||||
$this->backend->deleteByPrefix($cid);
|
||||
} else {
|
||||
// @todo This needs a map algorithm the same way memcache
|
||||
// module implemented it for invalidity by prefixes. This
|
||||
// is a very stupid fallback
|
||||
$this->setLastFlushTime(true);
|
||||
}
|
||||
}
|
||||
} else if (is_array($cid)) {
|
||||
$this->backend->deleteMultiple($cid);
|
||||
} else {
|
||||
$this->backend->delete($cid);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* From the given timestamp build an incremental safe time-based identifier.
|
||||
*
|
||||
* Due to potential accidental cache wipes, when a server goes down in the
|
||||
* cluster or when a server triggers its LRU algorithm wipe-out, keys that
|
||||
* matches flush or tags checksum might be dropped.
|
||||
*
|
||||
* Per default, each new inserted tag will trigger a checksum computation to
|
||||
* be stored in the Redis server as a timestamp. In order to ensure a checksum
|
||||
* validity a simple comparison between the tag checksum and the cache entry
|
||||
* checksum will tell us if the entry pre-dates the current checksum or not,
|
||||
* thus telling us its state. The main problem we experience is that Redis
|
||||
* is being so fast it is able to create and drop entries at same second,
|
||||
* sometime even the same micro second. The only safe way to avoid conflicts
|
||||
* is to checksum using an arbitrary computed number (a sequence).
|
||||
*
|
||||
* Drupal core does exactly this thus tags checksums are additions of each tag
|
||||
* individual checksum; each tag checksum is a independent arbitrary serial
|
||||
* that gets incremented starting with 0 (no invalidation done yet) to n (n
|
||||
* invalidations) which grows over time. This way the checksum computation
|
||||
* always rises and we have a sensible default that works in all cases.
|
||||
*
|
||||
* This model works as long as you can ensure consistency for the serial
|
||||
* storage over time. Nevertheless, as explained upper, in our case this
|
||||
* serial might be dropped at some point for various valid technical reasons:
|
||||
* if we start over to 0, we may accidentally compute a checksum which already
|
||||
* existed in the past and make invalid entries turn back to valid again.
|
||||
*
|
||||
* In order to prevent this behavior, using a timestamp as part of the serial
|
||||
* ensures that we won't experience this problem in a time range wider than a
|
||||
* single second, which is safe enough for us. But using timestamp creates a
|
||||
* new problem: Redis is so fast that we can set or delete hundreds of entries
|
||||
* easily during the same second: an entry created then invalidated the same
|
||||
* second will create false positives (entry is being considered as valid) -
|
||||
* note that depending on the check algorithm, false negative may also happen
|
||||
* the same way. Therefore we need to have an abitrary serial value to be
|
||||
* incremented in order to enforce our checks to be more strict.
|
||||
*
|
||||
* The solution to both the first (the need for a time based checksum in case
|
||||
* of checksum data being dropped) and the second (the need to have an
|
||||
* arbitrary predictible serial value to avoid false positives or negatives)
|
||||
* we are combining the two: every checksum will be built this way:
|
||||
*
|
||||
* UNIXTIMESTAMP.SERIAL
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* 1429789217.017
|
||||
*
|
||||
* will reprensent the 17th invalidation of the 1429789217 exact second which
|
||||
* happened while writing this documentation. The next tag being invalidated
|
||||
* the same second will then have this checksum:
|
||||
*
|
||||
* 1429789217.018
|
||||
*
|
||||
* And so on...
|
||||
*
|
||||
* In order to make it consitent with PHP string and float comparison we need
|
||||
* to set fixed precision over the decimal, and store as a string to avoid
|
||||
* possible float precision problems when comparing.
|
||||
*
|
||||
* This algorithm is not fully failsafe, but allows us to proceed to 1000
|
||||
* operations on the same checksum during the same second, which is a
|
||||
* sufficiently great value to reduce the conflict probability to almost
|
||||
* zero for most uses cases.
|
||||
*
|
||||
* @param int|string $timestamp
|
||||
* "TIMESTAMP[.INCREMENT]" string
|
||||
*
|
||||
* @return string
|
||||
* The next "TIMESTAMP.INCREMENT" string.
|
||||
*/
|
||||
public function getNextIncrement($timestamp = null)
|
||||
{
|
||||
if (!$timestamp) {
|
||||
return time() . '.000';
|
||||
}
|
||||
|
||||
if (false !== ($pos = strpos($timestamp, '.'))) {
|
||||
$inc = substr($timestamp, $pos + 1, 3);
|
||||
|
||||
return ((int)$timestamp) . '.' . str_pad($inc + 1, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $timestamp . '.000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid checksum
|
||||
*
|
||||
* @param int|string $previous
|
||||
* "TIMESTAMP[.INCREMENT]" string
|
||||
*
|
||||
* @return string
|
||||
* The next "TIMESTAMP.INCREMENT" string.
|
||||
*
|
||||
* @see Redis_Cache::getNextIncrement()
|
||||
*/
|
||||
public function getValidChecksum($previous = null)
|
||||
{
|
||||
if (time() === (int)$previous) {
|
||||
return $this->getNextIncrement($previous);
|
||||
} else {
|
||||
return $this->getNextIncrement();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Real cache backend primitives. This functions will be used by the
|
||||
* Redis_Cache wrapper class that implements the high-level logic that
|
||||
* allows us to be Drupal compatible.
|
||||
*/
|
||||
interface Redis_Cache_BackendInterface extends Redis_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Defaut constructor
|
||||
*
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function __construct($client, $namespace);
|
||||
|
||||
/**
|
||||
* Get namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespace();
|
||||
|
||||
/**
|
||||
* Set last flush time
|
||||
*
|
||||
* @param int $time
|
||||
* @param boolean $volatile
|
||||
*/
|
||||
public function setLastFlushTimeFor($time, $volatile = false);
|
||||
|
||||
/**
|
||||
* Get last flush time
|
||||
*
|
||||
* @return int[]
|
||||
* First value is for non-volatile items, second value is for volatile items.
|
||||
*/
|
||||
public function getLastFlushTime();
|
||||
|
||||
/**
|
||||
* Get a single entry
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return stdClass
|
||||
* Cache entry or false if the entry does not exists.
|
||||
*/
|
||||
public function get($id);
|
||||
|
||||
/**
|
||||
* Get multiple entries
|
||||
*
|
||||
* @param string[] $idList
|
||||
*
|
||||
* @return stdClass[]
|
||||
* Existing cache entries keyed by id,
|
||||
*/
|
||||
public function getMultiple(array $idList);
|
||||
|
||||
/**
|
||||
* Set a single entry
|
||||
*
|
||||
* @param string $id
|
||||
* @param mixed $data
|
||||
* @param int $ttl
|
||||
* @param boolean $volatile
|
||||
*/
|
||||
public function set($id, $data, $ttl = null, $volatile = false);
|
||||
|
||||
/**
|
||||
* Delete a single entry
|
||||
*
|
||||
* @param string $cid
|
||||
*/
|
||||
public function delete($id);
|
||||
|
||||
/**
|
||||
* Delete multiple entries
|
||||
*
|
||||
* This method should not use a single DEL command but use a pipeline instead
|
||||
*
|
||||
* @param array $idList
|
||||
*/
|
||||
public function deleteMultiple(array $idList);
|
||||
|
||||
/**
|
||||
* Delete entries by prefix
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function deleteByPrefix($prefix);
|
||||
|
||||
/**
|
||||
* Flush all entries
|
||||
*/
|
||||
public function flush();
|
||||
|
||||
/**
|
||||
* Flush all entries marked as temporary
|
||||
*/
|
||||
public function flushVolatile();
|
||||
}
|
39
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php
Normal file
39
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Base.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* - Improve lua scripts by using SCAN family commands
|
||||
* - Deambiguate why we need the namespace only for flush*() operations
|
||||
* - Implement the isEmpty() method by using SCAN or KEYS
|
||||
*/
|
||||
abstract class Redis_Cache_Base extends Redis_AbstractBackend
|
||||
{
|
||||
/**
|
||||
* Lastest cache flush KEY name
|
||||
*/
|
||||
const LAST_FLUSH_KEY = '_last_flush';
|
||||
|
||||
/**
|
||||
* Delete by prefix lua script
|
||||
*/
|
||||
const EVAL_DELETE_PREFIX = <<<EOT
|
||||
local keys = redis.call("KEYS", ARGV[1])
|
||||
for i, k in ipairs(keys) do
|
||||
redis.call("DEL", k)
|
||||
end
|
||||
return 1
|
||||
EOT;
|
||||
|
||||
/**
|
||||
* Delete volatile by prefix lua script
|
||||
*/
|
||||
const EVAL_DELETE_VOLATILE = <<<EOT
|
||||
local keys = redis.call('KEYS', ARGV[1])
|
||||
for i, k in ipairs(keys) do
|
||||
if "1" == redis.call("HGET", k, "volatile") then
|
||||
redis.call("DEL", k)
|
||||
end
|
||||
end
|
||||
return 1
|
||||
EOT;
|
||||
}
|
149
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php
Normal file
149
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/PhpRedis.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis cache backend.
|
||||
*/
|
||||
class Redis_Cache_PhpRedis extends Redis_Cache_Base
|
||||
{
|
||||
public function setLastFlushTimeFor($time, $volatile = false)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
|
||||
if ($volatile) {
|
||||
$client->hset($key, 'volatile', $time);
|
||||
} else {
|
||||
$client->hmset($key, array(
|
||||
'permanent' => $time,
|
||||
'volatile' => $time,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
$values = $client->hmget($key, array("permanent", "volatile"));
|
||||
|
||||
if (empty($values) || !is_array($values)) {
|
||||
$ret = array(0, 0);
|
||||
} else {
|
||||
if (empty($values['permanent'])) {
|
||||
$values['permanent'] = 0;
|
||||
}
|
||||
if (empty($values['volatile'])) {
|
||||
$values['volatile'] = 0;
|
||||
}
|
||||
$ret = array($values['permanent'], $values['volatile']);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function get($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
$values = $client->hgetall($key);
|
||||
|
||||
// Recent versions of PhpRedis will return the Redis instance
|
||||
// instead of an empty array when the HGETALL target key does
|
||||
// not exists. I see what you did there.
|
||||
if (empty($values) || !is_array($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function getMultiple(array $idList)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$ret = array();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
foreach ($idList as $id) {
|
||||
$pipe->hgetall($this->getKey($id));
|
||||
}
|
||||
$replies = $pipe->exec();
|
||||
|
||||
foreach (array_values($idList) as $line => $id) {
|
||||
if (!empty($replies[$line]) && is_array($replies[$line])) {
|
||||
$ret[$id] = $replies[$line];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set($id, $data, $ttl = null, $volatile = false)
|
||||
{
|
||||
// Ensure TTL consistency: if the caller gives us an expiry timestamp
|
||||
// in the past the key will expire now and will never be read.
|
||||
// Behavior between Predis and PhpRedis seems to change here: when
|
||||
// setting a negative expire time, PhpRedis seems to ignore the
|
||||
// command and leave the key permanent.
|
||||
if (null !== $ttl && $ttl <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['volatile'] = (int)$volatile;
|
||||
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
$pipe->hmset($key, $data);
|
||||
|
||||
if (null !== $ttl) {
|
||||
$pipe->expire($key, $ttl);
|
||||
}
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->getClient()->del($this->getKey($id));
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $idList)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
foreach ($idList as $id) {
|
||||
$pipe->del($this->getKey($id));
|
||||
}
|
||||
// Don't care if something failed.
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function deleteByPrefix($prefix)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey($prefix . '*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, array($this->getKey('*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flushVolatile()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_VOLATILE, array($this->getKey('*')));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
145
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php
Normal file
145
sites/all/modules/contrib/dev/redis/lib/Redis/Cache/Predis.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis cache backend.
|
||||
*/
|
||||
class Redis_Cache_Predis extends Redis_Cache_Base
|
||||
{
|
||||
public function setLastFlushTimeFor($time, $volatile = false)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
|
||||
if ($volatile) {
|
||||
$client->hset($key, 'volatile', $time);
|
||||
} else {
|
||||
$client->hmset($key, array(
|
||||
'permanent' => $time,
|
||||
'volatile' => $time,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function getLastFlushTime()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey(self::LAST_FLUSH_KEY);
|
||||
$values = $client->hmget($key, array("permanent", "volatile"));
|
||||
|
||||
if (empty($values) || !is_array($values)) {
|
||||
$values = array(0, 0);
|
||||
} else {
|
||||
if (empty($values[0])) {
|
||||
$values[0] = 0;
|
||||
}
|
||||
if (empty($values[1])) {
|
||||
$values[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function get($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($id);
|
||||
$values = $client->hgetall($key);
|
||||
|
||||
// Recent versions of PhpRedis will return the Redis instance
|
||||
// instead of an empty array when the HGETALL target key does
|
||||
// not exists. I see what you did there.
|
||||
if (empty($values) || !is_array($values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function getMultiple(array $idList)
|
||||
{
|
||||
$ret = array();
|
||||
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
foreach ($idList as $id) {
|
||||
$pipe->hgetall($this->getKey($id));
|
||||
}
|
||||
$replies = $pipe->execute();
|
||||
|
||||
foreach (array_values($idList) as $line => $id) {
|
||||
// HGETALL signature seems to differ depending on Predis versions.
|
||||
// This was found just after Predis update. Even though I'm not sure
|
||||
// this comes from Predis or just because we're misusing it.
|
||||
if (!empty($replies[$line]) && is_array($replies[$line])) {
|
||||
$ret[$id] = $replies[$line];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function set($id, $data, $ttl = null, $volatile = false)
|
||||
{
|
||||
// Ensure TTL consistency: if the caller gives us an expiry timestamp
|
||||
// in the past the key will expire now and will never be read.
|
||||
// Behavior between Predis and PhpRedis seems to change here: when
|
||||
// setting a negative expire time, PhpRedis seems to ignore the
|
||||
// command and leave the key permanent.
|
||||
if (null !== $ttl && $ttl <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$key = $this->getKey($id);
|
||||
|
||||
$data['volatile'] = (int)$volatile;
|
||||
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
$pipe->hmset($key, $data);
|
||||
if (null !== $ttl) {
|
||||
$pipe->expire($key, $ttl);
|
||||
}
|
||||
$pipe->execute();
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey($id));
|
||||
}
|
||||
|
||||
public function deleteMultiple(array $idList)
|
||||
{
|
||||
$pipe = $this->getClient()->pipeline();
|
||||
foreach ($idList as $id) {
|
||||
$pipe->del($this->getKey($id));
|
||||
}
|
||||
$pipe->execute();
|
||||
}
|
||||
|
||||
public function deleteByPrefix($prefix)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey($prefix . '*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_PREFIX, 0, $this->getKey('*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function flushVolatile()
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$ret = $client->eval(self::EVAL_DELETE_VOLATILE, 0, $this->getKey('*'));
|
||||
if (1 != $ret) {
|
||||
trigger_error(sprintf("EVAL failed: %s", $client->getLastError()), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This typically brings 80..85% compression in ~20ms/mb write, 5ms/mb read.
|
||||
*/
|
||||
class Redis_CacheCompressed extends Redis_Cache implements DrupalCacheInterface
|
||||
{
|
||||
private $compressionSizeThreshold = 100;
|
||||
private $compressionRatio = 1;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($bin)
|
||||
{
|
||||
parent::__construct($bin);
|
||||
|
||||
$this->compressionSizeThreshold = (int)variable_get('cache_compression_size_threshold', 100);
|
||||
if ($this->compressionSizeThreshold < 0) {
|
||||
trigger_error('cache_compression_size_threshold must be 0 or a positive integer, negative value found, switching back to default 100', E_USER_WARNING);
|
||||
$this->compressionSizeThreshold = 100;
|
||||
}
|
||||
|
||||
// Minimum compression level (1) has good ratio in low time.
|
||||
$this->compressionRatio = (int)variable_get('cache_compression_ratio', 1);
|
||||
if ($this->compressionRatio < 1 || 9 < $this->compressionRatio) {
|
||||
trigger_error('cache_compression_ratio must be between 1 and 9, out of bounds value found, switching back to default 1', E_USER_WARNING);
|
||||
$this->compressionRatio = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntryHash($cid, $data, $expire = CACHE_PERMANENT)
|
||||
{
|
||||
$hash = parent::createEntryHash($cid, $data, $expire);
|
||||
|
||||
// Empiric level when compression makes sense.
|
||||
if (!$this->compressionSizeThreshold || strlen($hash['data']) > $this->compressionSizeThreshold) {
|
||||
|
||||
$hash['data'] = gzcompress($hash['data'], $this->compressionRatio);
|
||||
$hash['compressed'] = true;
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function expandEntry(array $values, $flushPerm, $flushVolatile)
|
||||
{
|
||||
if (!empty($values['data']) && !empty($values['compressed'])) {
|
||||
// Uncompress, suppress warnings e.g. for broken CRC32.
|
||||
$values['data'] = @gzuncompress($values['data']);
|
||||
|
||||
// In such cases, void the cache entry.
|
||||
if ($values['data'] === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::expandEntry($values, $flushPerm, $flushVolatile);
|
||||
}
|
||||
}
|
241
sites/all/modules/contrib/dev/redis/lib/Redis/Client.php
Normal file
241
sites/all/modules/contrib/dev/redis/lib/Redis/Client.php
Normal file
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
// It may happen we get here with no autoloader set during the Drupal core
|
||||
// early bootstrap phase, at cache backend init time.
|
||||
if (!interface_exists('Redis_Client_FactoryInterface')) {
|
||||
require_once dirname(__FILE__) . '/Client/FactoryInterface.php';
|
||||
require_once dirname(__FILE__) . '/Client/Manager.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* This static class only reason to exist is to tie Drupal global
|
||||
* configuration to OOP driven code of this module: it will handle
|
||||
* everything that must be read from global configuration and let
|
||||
* other components live without any existence of it
|
||||
*/
|
||||
class Redis_Client
|
||||
{
|
||||
/**
|
||||
* Cache implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_CACHE = 'Redis_Cache_';
|
||||
|
||||
/**
|
||||
* Lock implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_LOCK = 'Redis_Lock_';
|
||||
|
||||
/**
|
||||
* Cache implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_QUEUE = 'Redis_Queue_';
|
||||
|
||||
/**
|
||||
* Path implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_PATH = 'Redis_Path_';
|
||||
|
||||
/**
|
||||
* Client factory implementation namespace.
|
||||
*/
|
||||
const REDIS_IMPL_CLIENT = 'Redis_Client_';
|
||||
|
||||
/**
|
||||
* @var Redis_Client_Manager
|
||||
*/
|
||||
private static $manager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
static protected $globalPrefix;
|
||||
|
||||
/**
|
||||
* Get site default global prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getGlobalPrefix()
|
||||
{
|
||||
// Provide a fallback for multisite. This is on purpose not inside the
|
||||
// getPrefixForBin() function in order to decouple the unified prefix
|
||||
// variable logic and custom module related security logic, that is not
|
||||
// necessary for all backends. We can't just use HTTP_HOST, as multiple
|
||||
// hosts might be using the same database. Or, more commonly, a site
|
||||
// might not be a multisite at all, but might be using Drush leading to
|
||||
// a separate HTTP_HOST of 'default'. Likewise, we can't rely on
|
||||
// conf_path(), as settings.php might be modifying what database to
|
||||
// connect to. To mirror what core does with database caching we use
|
||||
// the DB credentials to inform our cache key.
|
||||
if (null === self::$globalPrefix) {
|
||||
if (isset($GLOBALS['db_url']) && is_string($GLOBALS['db_url'])) {
|
||||
// Drupal 6 specifics when using the cache_backport module, we
|
||||
// therefore cannot use \Database class to determine database
|
||||
// settings.
|
||||
self::$globalPrefix = md5($GLOBALS['db_url']);
|
||||
} else {
|
||||
require_once DRUPAL_ROOT . '/includes/database/database.inc';
|
||||
$dbInfo = Database::getConnectionInfo();
|
||||
$active = $dbInfo['default'];
|
||||
self::$globalPrefix = md5($active['host'] . $active['database'] . $active['prefix']['default']);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$globalPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global default prefix
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getDefaultPrefix($namespace = null)
|
||||
{
|
||||
$ret = null;
|
||||
|
||||
if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
|
||||
$ret = $GLOBALS['drupal_test_info']['test_run_id'];
|
||||
} else {
|
||||
$prefixes = variable_get('cache_prefix', null);
|
||||
|
||||
if (is_string($prefixes)) {
|
||||
// Variable can be a string which then considered as a default
|
||||
// behavior.
|
||||
$ret = $prefixes;
|
||||
} else if (null !== $namespace && isset($prefixes[$namespace])) {
|
||||
if (false !== $prefixes[$namespace]) {
|
||||
// If entry is set and not false an explicit prefix is set
|
||||
// for the bin.
|
||||
$ret = $prefixes[$namespace];
|
||||
} else {
|
||||
// If we have an explicit false it means no prefix whatever
|
||||
// is the default configuration.
|
||||
$ret = '';
|
||||
}
|
||||
} else {
|
||||
// Key is not set, we can safely rely on default behavior.
|
||||
if (isset($prefixes['default']) && false !== $prefixes['default']) {
|
||||
$ret = $prefixes['default'];
|
||||
} else {
|
||||
// When default is not set or an explicit false this means
|
||||
// no prefix.
|
||||
$ret = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ret)) {
|
||||
$ret = Redis_Client::getGlobalPrefix();
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client manager
|
||||
*
|
||||
* @return Redis_Client_Manager
|
||||
*/
|
||||
static public function getManager()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (null === self::$manager) {
|
||||
|
||||
$className = self::getClass(self::REDIS_IMPL_CLIENT);
|
||||
$factory = new $className();
|
||||
|
||||
// Build server list from conf
|
||||
$serverList = array();
|
||||
if (isset($conf['redis_servers'])) {
|
||||
$serverList = $conf['redis_servers'];
|
||||
}
|
||||
|
||||
if (empty($serverList) || !isset($serverList['default'])) {
|
||||
|
||||
// Backward configuration compatibility with older versions
|
||||
$serverList[Redis_Client_Manager::REALM_DEFAULT] = array();
|
||||
|
||||
foreach (array('host', 'port', 'base', 'password', 'socket') as $key) {
|
||||
if (isset($conf['redis_client_' . $key])) {
|
||||
$serverList[Redis_Client_Manager::REALM_DEFAULT][$key] = $conf['redis_client_' . $key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$manager = new Redis_Client_Manager($factory, $serverList);
|
||||
}
|
||||
|
||||
return self::$manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find client class name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static public function getClientInterfaceName()
|
||||
{
|
||||
global $conf;
|
||||
|
||||
if (!empty($conf['redis_client_interface'])) {
|
||||
return $conf['redis_client_interface'];
|
||||
} else if (class_exists('Predis\Client')) {
|
||||
// Transparent and abitrary preference for Predis library.
|
||||
return $conf['redis_client_interface'] = 'Predis';
|
||||
} else if (class_exists('Redis')) {
|
||||
// Fallback on PhpRedis if available.
|
||||
return $conf['redis_client_interface'] = 'PhpRedis';
|
||||
} else {
|
||||
throw new Exception("No client interface set.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit test use only
|
||||
*/
|
||||
static public function reset(Redis_Client_Manager $manager = null)
|
||||
{
|
||||
self::$manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client for the 'default' realm
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getClient()
|
||||
{
|
||||
return self::getManager()->getClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific class implementing the current client usage for the specific
|
||||
* asked core subsystem.
|
||||
*
|
||||
* @param string $system
|
||||
* One of the Redis_Client::IMPL_* constant.
|
||||
* @param string $clientName
|
||||
* Client name, if fixed.
|
||||
*
|
||||
* @return string
|
||||
* Class name, if found.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
static public function getClass($system)
|
||||
{
|
||||
$class = $system . self::getClientInterfaceName();
|
||||
|
||||
if (!class_exists($class)) {
|
||||
throw new Exception(sprintf("Class '%s' does not exist", $class));
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client proxy, client handling class tied to the bare mininum.
|
||||
*/
|
||||
interface Redis_Client_FactoryInterface {
|
||||
/**
|
||||
* Get the connected client instance.
|
||||
*
|
||||
* @param array $options
|
||||
* Options from the server pool configuration that may contain:
|
||||
* - host
|
||||
* - port
|
||||
* - database
|
||||
* - password
|
||||
* - socket
|
||||
*
|
||||
* @return mixed
|
||||
* Real client depends from the library behind.
|
||||
*/
|
||||
public function getClient($options = array());
|
||||
|
||||
/**
|
||||
* Get underlaying library name used.
|
||||
*
|
||||
* This can be useful for contribution code that may work with only some of
|
||||
* the provided clients.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
144
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php
Normal file
144
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Manager.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Client pool manager for multi-server configurations
|
||||
*/
|
||||
class Redis_Client_Manager
|
||||
{
|
||||
/**
|
||||
* Redis default host
|
||||
*/
|
||||
const REDIS_DEFAULT_HOST = '127.0.0.1';
|
||||
|
||||
/**
|
||||
* Redis default port
|
||||
*/
|
||||
const REDIS_DEFAULT_PORT = 6379;
|
||||
|
||||
/**
|
||||
* Redis default socket (will override host and port)
|
||||
*/
|
||||
const REDIS_DEFAULT_SOCKET = null;
|
||||
|
||||
/**
|
||||
* Redis default database: will select none (Database 0)
|
||||
*/
|
||||
const REDIS_DEFAULT_BASE = null;
|
||||
|
||||
/**
|
||||
* Redis default password: will not authenticate
|
||||
*/
|
||||
const REDIS_DEFAULT_PASSWORD = null;
|
||||
|
||||
/**
|
||||
* Default realm
|
||||
*/
|
||||
const REALM_DEFAULT = 'default';
|
||||
|
||||
/**
|
||||
* Client interface name (PhpRedis or Predis)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $interfaceName;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
*/
|
||||
private $serverList = array();
|
||||
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
private $clients = array();
|
||||
|
||||
/**
|
||||
* @var Redis_Client_FactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param Redis_Client_FactoryInterface $factory
|
||||
* Client factory
|
||||
* @param array $serverList
|
||||
* Server connection info list
|
||||
*/
|
||||
public function __construct(Redis_Client_FactoryInterface $factory, $serverList = array())
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->serverList = $serverList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client for the given realm
|
||||
*
|
||||
* @param string $realm
|
||||
* @param boolean $allowDefault
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getClient($realm = self::REALM_DEFAULT, $allowDefault = true)
|
||||
{
|
||||
if (!isset($this->clients[$realm])) {
|
||||
$client = $this->createClient($realm);
|
||||
|
||||
if (false === $client) {
|
||||
if (self::REALM_DEFAULT !== $realm && $allowDefault) {
|
||||
$this->clients[$realm] = $this->getClient(self::REALM_DEFAULT);
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf("Could not find client for realm '%s'", $realm));
|
||||
}
|
||||
} else {
|
||||
$this->clients[$realm] = $client;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->clients[$realm];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build connection parameters array from current Drupal settings
|
||||
*
|
||||
* @param string $realm
|
||||
*
|
||||
* @return boolean|string[]
|
||||
* A key-value pairs of configuration values or false if realm is
|
||||
* not defined per-configuration
|
||||
*/
|
||||
private function buildOptions($realm)
|
||||
{
|
||||
$info = null;
|
||||
|
||||
if (isset($this->serverList[$realm])) {
|
||||
$info = $this->serverList[$realm];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$info += array(
|
||||
'host' => self::REDIS_DEFAULT_HOST,
|
||||
'port' => self::REDIS_DEFAULT_PORT,
|
||||
'base' => self::REDIS_DEFAULT_BASE,
|
||||
'password' => self::REDIS_DEFAULT_PASSWORD,
|
||||
'socket' => self::REDIS_DEFAULT_SOCKET
|
||||
);
|
||||
|
||||
return array_filter($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get client singleton
|
||||
*/
|
||||
private function createClient($realm)
|
||||
{
|
||||
$info = $this->buildOptions($realm);
|
||||
|
||||
if (false === $info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->factory->getClient($info);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis client specific implementation.
|
||||
*/
|
||||
class Redis_Client_PhpRedis implements Redis_Client_FactoryInterface {
|
||||
|
||||
public function getClient($options = array()) {
|
||||
$client = new Redis;
|
||||
|
||||
if (!empty($options['socket'])) {
|
||||
$client->connect($options['socket']);
|
||||
} else {
|
||||
$client->connect($options['host'], $options['port']);
|
||||
}
|
||||
|
||||
if (isset($options['password'])) {
|
||||
$client->auth($options['password']);
|
||||
}
|
||||
|
||||
if (isset($options['base'])) {
|
||||
$client->select($options['base']);
|
||||
}
|
||||
|
||||
// Do not allow PhpRedis serialize itself data, we are going to do it
|
||||
// ourself. This will ensure less memory footprint on Redis size when
|
||||
// we will attempt to store small values.
|
||||
$client->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
145
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php
Normal file
145
sites/all/modules/contrib/dev/redis/lib/Redis/Client/Predis.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis client specific implementation.
|
||||
*/
|
||||
class Redis_Client_Predis implements Redis_Client_FactoryInterface {
|
||||
|
||||
/**
|
||||
* Circular depedency breaker.
|
||||
*/
|
||||
static protected $autoloaderRegistered = false;
|
||||
|
||||
/**
|
||||
* If the first cache get operation happens after the core autoloader has
|
||||
* been registered to PHP, during our autoloader registration we will
|
||||
* trigger it when calling class_exists(): core autoloader will then run
|
||||
* cache_get() during autoloading but sadly this will run our autoloader
|
||||
* registration once again. The second time we are called the circular
|
||||
* dependency breaker will act and we will do nothing, ending up in a
|
||||
* class instanciation attempt while the autoloader is still not loaded.
|
||||
*/
|
||||
static protected $stupidCoreWorkaround = 0;
|
||||
|
||||
/**
|
||||
* Define Predis base path if not already set, and if we need to set the
|
||||
* autoloader by ourself. This will ensure no crash. Best way would have
|
||||
* been that Drupal ships a PSR-0 autoloader, in which we could manually
|
||||
* add our library path.
|
||||
*
|
||||
* We cannot do that in the file header, PHP class_exists() function wont
|
||||
* see classes being loaded during the autoloading because this file is
|
||||
* loaded by another autoloader: attempting the class_exists() during a
|
||||
* pending autoloading would cause PHP to crash and ignore the rest of the
|
||||
* file silentely (WTF!?). By delaying this at the getClient() call we
|
||||
* ensure we are not in the class loading process anymore.
|
||||
*/
|
||||
public static function setPredisAutoload() {
|
||||
|
||||
if (self::$autoloaderRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$stupidCoreWorkaround++;
|
||||
|
||||
// If you attempt to set Drupal's bin cache_bootstrap using Redis, you
|
||||
// will experience an infinite loop (breaking by itself the second time
|
||||
// it passes by): the following call will wake up autoloaders (and we
|
||||
// want that to work since user may have set its own autoloader) but
|
||||
// will wake up Drupal's one too, and because Drupal core caches its
|
||||
// file map, this will trigger this method to be called a second time
|
||||
// and boom! Adios bye bye. That's why this will be called early in the
|
||||
// 'redis.autoload.inc' file instead.
|
||||
if (1 < self::$stupidCoreWorkaround || !class_exists('Predis\Client')) {
|
||||
|
||||
if (!defined('PREDIS_BASE_PATH')) {
|
||||
$search = DRUPAL_ROOT . '/sites/all/libraries/predis';
|
||||
define('PREDIS_BASE_PATH', $search);
|
||||
} else {
|
||||
$search = PREDIS_BASE_PATH;
|
||||
}
|
||||
|
||||
if (is_dir($search . '/src')) { // Predis v1.x
|
||||
define('PREDIS_VERSION_MAJOR', 1);
|
||||
} else if (is_dir($search . '/lib')) { // Predis v0.x
|
||||
define('PREDIS_VERSION_MAJOR', 0);
|
||||
} else {
|
||||
throw new Exception("PREDIS_BASE_PATH constant must be set, Predis library must live in sites/all/libraries/predis.");
|
||||
}
|
||||
|
||||
// Register a simple autoloader for Predis library. Since the Predis
|
||||
// library is PHP 5.3 only, we can afford doing closures safely.
|
||||
switch (PREDIS_VERSION_MAJOR) {
|
||||
|
||||
case 0:
|
||||
$autoload = function($classname) { // PSR-0 autoloader.
|
||||
if (0 === strpos($classname, 'Predis\\')) {
|
||||
$filename = PREDIS_BASE_PATH . '/lib/' . str_replace('\\', '/', $classname) . '.php';
|
||||
return (bool)require_once $filename;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Register a simple autoloader for Predis library. Since the Predis
|
||||
// library is PHP 5.3 only, we can afford doing closures safely.
|
||||
$autoload = function($classname) { // PSR-4 autoloader
|
||||
if (0 === strpos($classname, 'Predis\\')) {
|
||||
$filename = PREDIS_BASE_PATH . '/src/' . str_replace('\\', '/', substr($classname, 7)) . '.php';
|
||||
return (bool)require_once $filename;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if ($autoload) {
|
||||
spl_autoload_register($autoload);
|
||||
}
|
||||
|
||||
// Same reason why we have the stupid core workaround, if this happens
|
||||
// during a second autoload call, PHP won't call the newly registered
|
||||
// autoloader function, so just load the file.
|
||||
if (1 < self::$stupidCoreWorkaround) {
|
||||
call_user_func($autoload, 'Predis\Client');
|
||||
}
|
||||
}
|
||||
|
||||
self::$autoloaderRegistered = true;
|
||||
}
|
||||
|
||||
public function getClient($options = array()) {
|
||||
|
||||
self::setPredisAutoload();
|
||||
|
||||
if (!empty($options['socket'])) {
|
||||
$options['scheme'] = 'unix';
|
||||
$options['path'] = $options['socket'];
|
||||
}
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (!isset($value)) {
|
||||
unset($options[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// I'm not sure why but the error handler is driven crazy if timezone
|
||||
// is not set at this point.
|
||||
// Hopefully Drupal will restore the right one this once the current
|
||||
// account has logged in.
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
|
||||
$client = new \Predis\Client($options);
|
||||
|
||||
if (isset($options['base']) && 0 !== $options['base']) {
|
||||
$client->select((int)$options['base']);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
31
sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php
Normal file
31
sites/all/modules/contrib/dev/redis/lib/Redis/Lock.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend singleton handling.
|
||||
*/
|
||||
class Redis_Lock {
|
||||
/**
|
||||
* @var Redis_Lock_BackendInterface
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Get actual lock backend.
|
||||
*
|
||||
* @return Redis_Lock_BackendInterface
|
||||
*/
|
||||
public static function getBackend()
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK);
|
||||
|
||||
self::$instance = new $className(
|
||||
Redis_Client::getClient(),
|
||||
Redis_Client::getDefaultPrefix('lock')
|
||||
);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend interface.
|
||||
*/
|
||||
interface Redis_Lock_BackendInterface {
|
||||
/**
|
||||
* Acquire lock.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name.
|
||||
* @param float $timeout = 30.0
|
||||
* (optional) Lock lifetime in seconds.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lockAcquire($name, $timeout = 30.0);
|
||||
|
||||
/**
|
||||
* Check if lock is available for acquire.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock to acquire.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function lockMayBeAvailable($name);
|
||||
|
||||
/**
|
||||
* Wait a short amount of time before a second lock acquire attempt.
|
||||
*
|
||||
* @param string $name
|
||||
* Lock name currently being locked.
|
||||
* @param int $delay = 30
|
||||
* Miliseconds to wait for.
|
||||
*/
|
||||
public function lockWait($name, $delay = 30);
|
||||
|
||||
/**
|
||||
* Release given lock.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function lockRelease($name);
|
||||
|
||||
/**
|
||||
* Release all locks for the given lock token identifier.
|
||||
*
|
||||
* @param string $lockId = NULL
|
||||
* (optional) If none given, remove all lock from the current page.
|
||||
*/
|
||||
public function lockReleaseAll($lock_id = NULL);
|
||||
|
||||
/**
|
||||
* Get the unique page token for locks. Locks will be wipeout at each end of
|
||||
* page request on a token basis.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLockId();
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lock backend shared methods.
|
||||
*/
|
||||
abstract class Redis_Lock_DefaultBackend
|
||||
extends Redis_AbstractBackend
|
||||
implements Redis_Lock_BackendInterface
|
||||
{
|
||||
/**
|
||||
* Current page lock token identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_lockId;
|
||||
|
||||
/**
|
||||
* Existing locks for this page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_locks = array();
|
||||
|
||||
/**
|
||||
* Default implementation from actual Drupal core.
|
||||
*
|
||||
* @see Redis_Lock_BackendInterface::lockWait()
|
||||
*/
|
||||
public function lockWait($name, $delay = 30) {
|
||||
// Pause the process for short periods between calling
|
||||
// lock_may_be_available(). This prevents hitting the database with constant
|
||||
// database queries while waiting, which could lead to performance issues.
|
||||
// However, if the wait period is too long, there is the potential for a
|
||||
// large number of processes to be blocked waiting for a lock, especially
|
||||
// if the item being rebuilt is commonly requested. To address both of these
|
||||
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
|
||||
// time until it reaches 500ms. After this point polling will continue every
|
||||
// 500ms until $delay is reached.
|
||||
|
||||
// $delay is passed in seconds, but we will be using usleep(), which takes
|
||||
// microseconds as a parameter. Multiply it by 1 million so that all
|
||||
// further numbers are equivalent.
|
||||
$delay = (int) $delay * 1000000;
|
||||
|
||||
// Begin sleeping at 25ms.
|
||||
$sleep = 25000;
|
||||
while ($delay > 0) {
|
||||
// This function should only be called by a request that failed to get a
|
||||
// lock, so we sleep first to give the parallel request a chance to finish
|
||||
// and release the lock.
|
||||
usleep($sleep);
|
||||
// After each sleep, increase the value of $sleep until it reaches
|
||||
// 500ms, to reduce the potential for a lock stampede.
|
||||
$delay = $delay - $sleep;
|
||||
$sleep = min(500000, $sleep + 25000, $delay);
|
||||
if ($this->lockMayBeAvailable($name)) {
|
||||
// No longer need to wait.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// The caller must still wait longer to get the lock.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation from actual Drupal core.
|
||||
*
|
||||
* @see Redis_Lock_BackendInterface::getLockId()
|
||||
*/
|
||||
public function getLockId() {
|
||||
if (!isset($this->_lockId)) {
|
||||
$this->_lockId = uniqid(mt_rand(), TRUE);
|
||||
// We only register a shutdown function if a lock is used.
|
||||
drupal_register_shutdown_function('lock_release_all', $this->_lockId);
|
||||
}
|
||||
return $this->_lockId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a redis key name for the current lock name
|
||||
*/
|
||||
public function getKey($name = null) {
|
||||
if (null === $name) {
|
||||
return parent::getKey('lock');
|
||||
} else {
|
||||
return parent::getKey(array('lock', $name));
|
||||
}
|
||||
}
|
||||
}
|
138
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php
Normal file
138
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/PhpRedis.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis lock backend implementation.
|
||||
*
|
||||
* This implementation works with a single key per lock so is viable when
|
||||
* doing client side sharding and/or using consistent hashing algorithm.
|
||||
*/
|
||||
class Redis_Lock_PhpRedis extends Redis_Lock_DefaultBackend {
|
||||
|
||||
public function lockAcquire($name, $timeout = 30.0) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
// Insure that the timeout is at least 1 second, we cannot do otherwise with
|
||||
// Redis, this is a minor change to the function signature, but in real life
|
||||
// nobody will notice with so short duration.
|
||||
$timeout = ceil(max($timeout, 1));
|
||||
|
||||
// If we already have the lock, check for his owner and attempt a new EXPIRE
|
||||
// command on it.
|
||||
if (isset($this->_locks[$name])) {
|
||||
|
||||
// Create a new transaction, for atomicity.
|
||||
$client->watch($key);
|
||||
|
||||
// Global tells us we are the owner, but in real life it could have expired
|
||||
// and another process could have taken it, check that.
|
||||
if ($client->get($key) != $id) {
|
||||
// Explicit UNWATCH we are not going to run the MULTI/EXEC block.
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// See https://github.com/phpredis/phpredis#watch-unwatch
|
||||
// MULTI and other commands can fail, so we can't chain calls.
|
||||
if (FALSE !== ($result = $client->multi())) {
|
||||
$client->setex($key, $timeout, $id);
|
||||
$result = $client->exec();
|
||||
}
|
||||
|
||||
// Did it broke?
|
||||
if (FALSE === $result) {
|
||||
unset($this->_locks[$name]);
|
||||
// Explicit transaction release which also frees the WATCH'ed key.
|
||||
$client->discard();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
else {
|
||||
$client->watch($key);
|
||||
$owner = $client->get($key);
|
||||
|
||||
// If the $key is set they lock is not available
|
||||
if (!empty($owner) && $id != $owner) {
|
||||
$client->unwatch();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// See https://github.com/phpredis/phpredis#watch-unwatch
|
||||
// MULTI and other commands can fail, so we can't chain calls.
|
||||
if (FALSE !== ($result = $client->multi())) {
|
||||
$client->setex($key, $timeout, $id);
|
||||
$result->exec();
|
||||
}
|
||||
|
||||
// If another client modified the $key value, transaction will be discarded
|
||||
// $result will be set to FALSE. This means atomicity have been broken and
|
||||
// the other client took the lock instead of us.
|
||||
if (FALSE === $result) {
|
||||
// Explicit transaction release which also frees the WATCH'ed key.
|
||||
$client->discard();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Register the lock.
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function lockMayBeAvailable($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
$value = $client->get($key);
|
||||
|
||||
return FALSE === $value || $id == $value;
|
||||
}
|
||||
|
||||
public function lockRelease($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
unset($this->_locks[$name]);
|
||||
|
||||
// Ensure the lock deletion is an atomic transaction. If another thread
|
||||
// manages to removes all lock, we can not alter it anymore else we will
|
||||
// release the lock for the other thread and cause race conditions.
|
||||
$client->watch($key);
|
||||
|
||||
if ($client->get($key) == $id) {
|
||||
$client->multi();
|
||||
$client->delete($key);
|
||||
$client->exec();
|
||||
}
|
||||
else {
|
||||
$client->unwatch();
|
||||
}
|
||||
}
|
||||
|
||||
public function lockReleaseAll($lock_id = NULL) {
|
||||
if (!isset($lock_id) && empty($this->_locks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $this->getClient();
|
||||
$id = isset($lock_id) ? $lock_id : $this->getLockId();
|
||||
|
||||
// We can afford to deal with a slow algorithm here, this should not happen
|
||||
// on normal run because we should have removed manually all our locks.
|
||||
foreach (array_keys($this->_locks) as $name) {
|
||||
$key = $this->getKey($name);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (empty($owner) || $owner == $id) {
|
||||
$client->delete($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
137
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php
Normal file
137
sites/all/modules/contrib/dev/redis/lib/Redis/Lock/Predis.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Predis lock backend implementation.
|
||||
*
|
||||
* This implementation works with a single key per lock so is viable when
|
||||
* doing client side sharding and/or using consistent hashing algorithm.
|
||||
*/
|
||||
class Redis_Lock_Predis extends Redis_Lock_DefaultBackend {
|
||||
|
||||
public function lockAcquire($name, $timeout = 30.0) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
// Insure that the timeout is at least 1 second, we cannot do otherwise with
|
||||
// Redis, this is a minor change to the function signature, but in real life
|
||||
// nobody will notice with so short duration.
|
||||
$timeout = ceil(max($timeout, 1));
|
||||
|
||||
// If we already have the lock, check for his owner and attempt a new EXPIRE
|
||||
// command on it.
|
||||
if (isset($this->_locks[$name])) {
|
||||
|
||||
// Create a new transaction, for atomicity.
|
||||
$client->watch($key);
|
||||
|
||||
// Global tells us we are the owner, but in real life it could have expired
|
||||
// and another process could have taken it, check that.
|
||||
if ($client->get($key) != $id) {
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
|
||||
$pipe->multi();
|
||||
$pipe->setex($key, $timeout, $id);
|
||||
$pipe->exec();
|
||||
});
|
||||
|
||||
$execReply = array_pop($replies);
|
||||
|
||||
if (FALSE === $execReply[0]) {
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$client->watch($key);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (!empty($owner) && $owner != $id) {
|
||||
$client->unwatch();
|
||||
unset($this->_locks[$name]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$replies = $client->pipeline(function($pipe) use ($key, $timeout, $id) {
|
||||
$pipe->multi();
|
||||
$pipe->setex($key, $timeout, $id);
|
||||
$pipe->exec();
|
||||
});
|
||||
|
||||
$execReply = array_pop($replies);
|
||||
|
||||
// If another client modified the $key value, transaction will be discarded
|
||||
// $result will be set to FALSE. This means atomicity have been broken and
|
||||
// the other client took the lock instead of us.
|
||||
// EXPIRE and SETEX won't return something here, EXEC return is index 0
|
||||
// This was determined debugging, seems to be Predis specific.
|
||||
if (FALSE === $execReply[0]) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Register the lock and return.
|
||||
return ($this->_locks[$name] = TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
public function lockMayBeAvailable($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
$value = $client->get($key);
|
||||
|
||||
return empty($value) || $id == $value;
|
||||
}
|
||||
|
||||
public function lockRelease($name) {
|
||||
$client = $this->getClient();
|
||||
$key = $this->getKey($name);
|
||||
$id = $this->getLockId();
|
||||
|
||||
unset($this->_locks[$name]);
|
||||
|
||||
// Ensure the lock deletion is an atomic transaction. If another thread
|
||||
// manages to removes all lock, we can not alter it anymore else we will
|
||||
// release the lock for the other thread and cause race conditions.
|
||||
$client->watch($key);
|
||||
|
||||
if ($client->get($key) == $id) {
|
||||
$client->multi();
|
||||
$client->del(array($key));
|
||||
$client->exec();
|
||||
}
|
||||
else {
|
||||
$client->unwatch();
|
||||
}
|
||||
}
|
||||
|
||||
public function lockReleaseAll($lock_id = NULL) {
|
||||
if (!isset($lock_id) && empty($this->_locks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$client = $this->getClient();
|
||||
$id = isset($lock_id) ? $lock_id : $this->getLockId();
|
||||
|
||||
// We can afford to deal with a slow algorithm here, this should not happen
|
||||
// on normal run because we should have removed manually all our locks.
|
||||
foreach (array_keys($this->_locks) as $name) {
|
||||
$key = $this->getKey($name);
|
||||
$owner = $client->get($key);
|
||||
|
||||
if (empty($owner) || $owner == $id) {
|
||||
$client->del(array($key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Common implementation for Redis-based implementations
|
||||
*/
|
||||
abstract class Redis_Path_AbstractHashLookup extends Redis_AbstractBackend implements
|
||||
Redis_Path_HashLookupInterface
|
||||
{
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $hkey
|
||||
* @param string $hvalue
|
||||
*/
|
||||
abstract protected function saveInHash($key, $hkey, $hvalue);
|
||||
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $hkey
|
||||
* @param string $hvalue
|
||||
*/
|
||||
abstract protected function deleteInHash($key, $hkey, $hvalue);
|
||||
|
||||
/**
|
||||
* @todo document me
|
||||
*
|
||||
* @param string $keyPrefix
|
||||
* @param string $hkey
|
||||
* @param string $language
|
||||
*/
|
||||
abstract protected function lookupInHash($keyPrefix, $hkey, $language = null);
|
||||
|
||||
/**
|
||||
* Normalize value to avoid duplicate or false negatives
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function normalize($value)
|
||||
{
|
||||
if (null !== $value) {
|
||||
return strtolower(trim($value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveAlias($source, $alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
$source = $this->normalize($source);
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
}
|
||||
|
||||
if (!empty($source)) {
|
||||
$this->saveInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias);
|
||||
}
|
||||
if (!empty($alias)) {
|
||||
$this->saveInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteAlias($source, $alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
$source = $this->normalize($source);
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
}
|
||||
|
||||
$this->deleteInHash($this->getKey(array(self::KEY_ALIAS, $language)), $source, $alias);
|
||||
$this->deleteInHash($this->getKey(array(self::KEY_SOURCE, $language)), $alias, $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupAlias($source, $language = null)
|
||||
{
|
||||
$source = $this->normalize($source);
|
||||
|
||||
return $this->lookupInHash(self::KEY_ALIAS, $source, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupSource($alias, $language = null)
|
||||
{
|
||||
$alias = $this->normalize($alias);
|
||||
|
||||
return $this->lookupInHash(self::KEY_SOURCE, $alias, $language);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Very fast hash based lookup interface.
|
||||
*
|
||||
* This will work for any key-value store whether it's APC, Redis, memcache...
|
||||
* Rationale behind this is that Drupal calls hundreds of time per request the
|
||||
* drupal_lookup_path() function and we need it to be very fast. The key of
|
||||
* success to keep it stupid simple and coherent as the same time is that we
|
||||
* consider this backend as a cache (more or less permanent) that might be
|
||||
* cleared at any time, and synchronized as when necessary or incrementally.
|
||||
* This should be very fast.
|
||||
*
|
||||
* Redis implementation will be the following:
|
||||
*
|
||||
* Aliases are stored into a Redis HASH and are stored per language basis.
|
||||
* Key is:
|
||||
* [SITEPREFIX:]path:dst:LANGUAGE
|
||||
* Keys inside the hash are a MD5() of the source and values are the alias
|
||||
*
|
||||
* Sources are also stored the same way except the HASH key is the following:
|
||||
* [SITEPREFIX:]path:src:LANGUAGE
|
||||
* Keys inside the hash are a MD5() of the alias and values are the sources.
|
||||
*
|
||||
* In both case values are a comma separated list of string values.
|
||||
*
|
||||
* The MD5() should give us low collision algorithm and we'll keep it until
|
||||
* no one experiences any problem.
|
||||
*
|
||||
* Alias and sources are always looked up using the language, hence the
|
||||
* different keys for different languages.
|
||||
*/
|
||||
interface Redis_Path_HashLookupInterface
|
||||
{
|
||||
/**
|
||||
* Alias HASH key prefix
|
||||
*/
|
||||
const KEY_ALIAS = 'path:a';
|
||||
|
||||
/**
|
||||
* Source HASH key prefix
|
||||
*/
|
||||
const KEY_SOURCE = 'path:s';
|
||||
|
||||
/**
|
||||
* Null value (not existing yet cached value)
|
||||
*/
|
||||
const VALUE_NULL = '!';
|
||||
|
||||
/**
|
||||
* Values separator for hash values
|
||||
*/
|
||||
const VALUE_SEPARATOR = '#';
|
||||
|
||||
/**
|
||||
* Alias is being inserted with the given source
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*/
|
||||
public function saveAlias($source, $alias, $language = null);
|
||||
|
||||
/**
|
||||
* Alias is being deleted for the given source
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*/
|
||||
public function deleteAlias($source, $alias, $language = null);
|
||||
|
||||
/**
|
||||
* A language is being deleted
|
||||
*
|
||||
* @param string $language
|
||||
*/
|
||||
public function deleteLanguage($language);
|
||||
|
||||
/**
|
||||
* Lookup any alias for the given source
|
||||
*
|
||||
* First that has been inserted wins over the others
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $language
|
||||
*
|
||||
* @return string|null|false
|
||||
* - The string value if found
|
||||
* - null if not found
|
||||
* - false if set as non existing
|
||||
*/
|
||||
public function lookupAlias($source, $language = null);
|
||||
|
||||
/**
|
||||
* Lookup any source for the given alias
|
||||
*
|
||||
* First that has been inserted wins over the others
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $language
|
||||
*
|
||||
* @return string|null|false
|
||||
* - The string value if found
|
||||
* - null if not found
|
||||
* - false if set as non existing
|
||||
*/
|
||||
public function lookupSource($alias, $language = null);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Null implementation.
|
||||
*/
|
||||
class Redis_Path_NullHashLookup implements Redis_Path_HashLookupInterface
|
||||
{
|
||||
public function saveAlias($source, $alias, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteAlias($source, $alias, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
}
|
||||
|
||||
public function lookupAlias($source, $language = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function lookupSource($alias, $language = null)
|
||||
{
|
||||
}
|
||||
}
|
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php
Normal file
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/PhpRedis.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis implementation.
|
||||
*
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Path_PhpRedis extends Redis_Path_AbstractHashLookup
|
||||
{
|
||||
protected function saveInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value === self::VALUE_NULL) { // Remove any null values
|
||||
$value = null;
|
||||
}
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (!in_array($hvalue, $existing)) {
|
||||
// Prepend the most recent path to ensure it always be
|
||||
// first fetched one
|
||||
// @todo Ensure in case of update that its position does
|
||||
// not changes (pid ordering in Drupal core)
|
||||
$value = $hvalue . self::VALUE_SEPARATOR . $value;
|
||||
} else { // Do nothing on empty value
|
||||
$value = null;
|
||||
}
|
||||
} else if (empty($hvalue)) {
|
||||
$value = self::VALUE_NULL;
|
||||
} else {
|
||||
$value = $hvalue;
|
||||
}
|
||||
|
||||
if (!empty($value)) {
|
||||
$client->hset($key, $hkey, $value);
|
||||
}
|
||||
// Empty value here means that we already got it
|
||||
}
|
||||
|
||||
protected function deleteInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (false !== ($index = array_search($hvalue, $existing))) {
|
||||
if (1 === count($existing)) {
|
||||
$client->hdel($key, $hkey);
|
||||
} else {
|
||||
unset($existing[$index]);
|
||||
$client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function lookupInHash($keyPrefix, $hkey, $language = null)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
$doNoneLookup = false;
|
||||
} else if (LANGUAGE_NONE === $language) {
|
||||
$doNoneLookup = false;
|
||||
} else {
|
||||
$doNoneLookup = true;
|
||||
}
|
||||
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey);
|
||||
if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) {
|
||||
$previous = $ret;
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey);
|
||||
if (!$ret || self::VALUE_NULL === $ret) {
|
||||
// Restore null placeholder else we loose conversion to false
|
||||
// and drupal_lookup_path() would attempt saving it once again
|
||||
$ret = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::VALUE_NULL === $ret) {
|
||||
return false; // Needs conversion
|
||||
}
|
||||
if (empty($ret)) {
|
||||
return null; // Value not found
|
||||
}
|
||||
|
||||
$existing = explode(self::VALUE_SEPARATOR, $ret);
|
||||
|
||||
return reset($existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey(array(self::KEY_ALIAS, $language)));
|
||||
$client->del($this->getKey(array(self::KEY_SOURCE, $language)));
|
||||
}
|
||||
}
|
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php
Normal file
108
sites/all/modules/contrib/dev/redis/lib/Redis/Path/Predis.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* PhpRedis implementation.
|
||||
*
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Path_Predis extends Redis_Path_AbstractHashLookup
|
||||
{
|
||||
protected function saveInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value === self::VALUE_NULL) { // Remove any null values
|
||||
$value = null;
|
||||
}
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (!in_array($hvalue, $existing)) {
|
||||
// Prepend the most recent path to ensure it always be
|
||||
// first fetched one
|
||||
// @todo Ensure in case of update that its position does
|
||||
// not changes (pid ordering in Drupal core)
|
||||
$value = $hvalue . self::VALUE_SEPARATOR . $value;
|
||||
} else { // Do nothing on empty value
|
||||
$value = null;
|
||||
}
|
||||
} else if (empty($hvalue)) {
|
||||
$value = self::VALUE_NULL;
|
||||
} else {
|
||||
$value = $hvalue;
|
||||
}
|
||||
|
||||
if (!empty($value)) {
|
||||
$client->hset($key, $hkey, $value);
|
||||
}
|
||||
// Empty value here means that we already got it
|
||||
}
|
||||
|
||||
protected function deleteInHash($key, $hkey, $hvalue)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$value = $client->hget($key, $hkey);
|
||||
|
||||
if ($value) {
|
||||
$existing = explode(self::VALUE_SEPARATOR, $value);
|
||||
if (false !== ($index = array_search($hvalue, $existing))) {
|
||||
if (1 === count($existing)) {
|
||||
$client->hdel($key, $hkey);
|
||||
} else {
|
||||
unset($existing[$index]);
|
||||
$client->hset($key, $hkey, implode(self::VALUE_SEPARATOR, $existing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function lookupInHash($keyPrefix, $hkey, $language = null)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
if (null === $language) {
|
||||
$language = LANGUAGE_NONE;
|
||||
$doNoneLookup = false;
|
||||
} else if (LANGUAGE_NONE === $language) {
|
||||
$doNoneLookup = false;
|
||||
} else {
|
||||
$doNoneLookup = true;
|
||||
}
|
||||
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, $language)), $hkey);
|
||||
if ($doNoneLookup && (!$ret || self::VALUE_NULL === $ret)) {
|
||||
$previous = $ret;
|
||||
$ret = $client->hget($this->getKey(array($keyPrefix, LANGUAGE_NONE)), $hkey);
|
||||
if (!$ret || self::VALUE_NULL === $ret) {
|
||||
// Restore null placeholder else we loose conversion to false
|
||||
// and drupal_lookup_path() would attempt saving it once again
|
||||
$ret = $previous;
|
||||
}
|
||||
}
|
||||
|
||||
if (self::VALUE_NULL === $ret) {
|
||||
return false; // Needs conversion
|
||||
}
|
||||
if (empty($ret)) {
|
||||
return null; // Value not found
|
||||
}
|
||||
|
||||
$existing = explode(self::VALUE_SEPARATOR, $ret);
|
||||
|
||||
return reset($existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteLanguage($language)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
$client->del($this->getKey(array(self::KEY_ALIAS, $language)));
|
||||
$client->del($this->getKey(array(self::KEY_SOURCE, $language)));
|
||||
}
|
||||
}
|
58
sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php
Normal file
58
sites/all/modules/contrib/dev/redis/lib/Redis/Queue.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
class Redis_Queue implements DrupalReliableQueueInterface
|
||||
{
|
||||
/**
|
||||
* @var DrupalQueueInterface
|
||||
*/
|
||||
protected $backend;
|
||||
|
||||
/**
|
||||
* Default contructor
|
||||
*
|
||||
* Beware that DrupalQueueInterface does not defines the __construct
|
||||
* method in the interface yet is being used from DrupalQueue::get()
|
||||
*
|
||||
* @param unknown $name
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_QUEUE);
|
||||
$this->backend = new $className(Redis_Client::getClient(), $name);
|
||||
}
|
||||
|
||||
public function createItem($data)
|
||||
{
|
||||
return $this->backend->createItem($data);
|
||||
}
|
||||
|
||||
public function numberOfItems()
|
||||
{
|
||||
return $this->backend->numberOfItems();
|
||||
}
|
||||
|
||||
public function claimItem($lease_time = 3600)
|
||||
{
|
||||
return $this->backend->claimItem($lease_time);
|
||||
}
|
||||
|
||||
public function deleteItem($item)
|
||||
{
|
||||
return $this->backend->deleteItem($item);
|
||||
}
|
||||
|
||||
public function releaseItem($item)
|
||||
{
|
||||
return $this->backend->releaseItem($item);
|
||||
}
|
||||
|
||||
public function createQueue()
|
||||
{
|
||||
return $this->backend->createQueue();
|
||||
}
|
||||
|
||||
public function deleteQueue()
|
||||
{
|
||||
return $this->backend->deleteQueue();
|
||||
}
|
||||
}
|
99
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php
Normal file
99
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/Base.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Redis allows implementing reliable queues, here is the spec:
|
||||
*
|
||||
* - For each queue, you have 4 different HASH:
|
||||
*
|
||||
* - One for queued items queue:NAME:queued
|
||||
*
|
||||
* - One for claimed items being processed: queue:NAME:claimed
|
||||
*
|
||||
* - One for claimed items leave time: queue:NAME:leave
|
||||
* Items from this one will be arbitrarily fetched at cron
|
||||
* time and released when leave is outdated.
|
||||
*
|
||||
* - One containing the item values and other valuable stateful
|
||||
* information: queue:NAME:data ;
|
||||
*
|
||||
* - For example, current job maximum identifier (auto increment
|
||||
* emulation) will be stored in the "sequence" HASH key
|
||||
*
|
||||
* - All other keys within the HASH will be the items themselves,
|
||||
* keys for those will always be numeric
|
||||
*
|
||||
* - Each time a queue will be emptied, even during a pragmatic process,
|
||||
* it will be automatically deleted, reseting the sequence counter to
|
||||
* the 0 value each time
|
||||
*
|
||||
* - Algorithm is a variation of the one described in "Reliable queue"
|
||||
* section of http://redis.io/commands/rpoplpush and partial port of what
|
||||
* you can find in the http://drupal.org/project/redis_queue module.
|
||||
*
|
||||
* You will find the driver specific implementation in the Redis_Queue_*
|
||||
* classes as they may differ in how the API handles transaction, pipelining
|
||||
* and return values.
|
||||
*/
|
||||
abstract class Redis_Queue_Base extends Redis_AbstractBackend implements
|
||||
DrupalReliableQueueInterface
|
||||
{
|
||||
/**
|
||||
* Key prefix for queue data.
|
||||
*/
|
||||
const QUEUE_KEY_PREFIX = 'queue';
|
||||
|
||||
/**
|
||||
* Data HASH sequence key name.
|
||||
*/
|
||||
const QUEUE_HKEY_SEQ = 'seq';
|
||||
|
||||
/**
|
||||
* Get data HASH key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForData()
|
||||
{
|
||||
return $this->getKey('data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued items LIST key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForQueue()
|
||||
{
|
||||
return $this->getKey('queued');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get claimed LIST key
|
||||
*
|
||||
* Key will already be prefixed
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKeyForClaimed()
|
||||
{
|
||||
return $this->getKey('claimed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Default contructor
|
||||
*
|
||||
* Beware that DrupalQueueInterface does not defines the __construct
|
||||
* method in the interface yet is being used from DrupalQueue::get()
|
||||
*
|
||||
* @param mixed $client
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct($client, $name)
|
||||
{
|
||||
parent::__construct($client, self::QUEUE_KEY_PREFIX . $name);
|
||||
}
|
||||
}
|
106
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php
Normal file
106
sites/all/modules/contrib/dev/redis/lib/Redis/Queue/PhpRedis.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Set high expire value to the hash for rotation when memory is empty
|
||||
* React upon cache clear all and rebuild path list?
|
||||
*/
|
||||
class Redis_Queue_PhpRedis extends Redis_Queue_Base
|
||||
{
|
||||
public function createItem($data)
|
||||
{
|
||||
$client = $this->getClient();
|
||||
|
||||
$dKey = $this->getKeyForData();
|
||||
$qKey = $this->getKeyForQueue();
|
||||
|
||||
// Identifier does not not need to be in the transaction,
|
||||
// in case of any error we'll just skip a value in the sequence.
|
||||
$id = $client->hincrby($dKey, self::QUEUE_HKEY_SEQ, 1);
|
||||
|
||||
$record = new stdClass();
|
||||
$record->qid = $id;
|
||||
$record->data = $data;
|
||||
$record->timestamp = time();
|
||||
|
||||
$pipe = $client->multi(Redis::PIPELINE);
|
||||
// Thanks to the redis_queue standalone module maintainers for
|
||||
// this piece of code, very effective. Note that we added the
|
||||
// pipeline thought.
|
||||
$pipe->hsetnx($dKey, $id, serialize($record));
|
||||
$pipe->llen($qKey);
|
||||
$pipe->lpush($qKey, $id);
|
||||
$ret = $pipe->exec();
|
||||
|
||||
if (!$success = ($ret[0] && $ret[1] < $ret[2])) {
|
||||
if ($ret[0]) {
|
||||
// HSETNEX worked but not the PUSH command we therefore
|
||||
// need to drop the inserted data. I would have prefered
|
||||
// a DISCARD instead but we are in pipelined transaction
|
||||
// we cannot actually do a DISCARD here.
|
||||
$client->hdel($dKey, $id);
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function numberOfItems()
|
||||
{
|
||||
return $this->getClient()->llen($this->getKeyForQueue());
|
||||
}
|
||||
|
||||
public function claimItem($lease_time = 30)
|
||||
{
|
||||
// @todo Deal with lease
|
||||
$client = $this->getClient();
|
||||
|
||||
$id = $client->rpoplpush(
|
||||
$this->getKeyForQueue(),
|
||||
$this->getKeyForClaimed()
|
||||
);
|
||||
|
||||
if ($id) {
|
||||
if ($item = $client->hget($this->getKeyForData(), $id)) {
|
||||
if ($item = unserialize($item)) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteItem($item)
|
||||
{
|
||||
$pipe = $this->getClient()->multi(Redis::PIPELINE);
|
||||
$pipe->lrem($this->getKeyForQueue(), $item->qid);
|
||||
$pipe->lrem($this->getKeyForClaimed(), $item->qid);
|
||||
$pipe->hdel($this->getKeyForData(), $item->qid);
|
||||
$pipe->exec();
|
||||
}
|
||||
|
||||
public function releaseItem($item)
|
||||
{
|
||||
$pipe = $this->getClient()->multi(Redis::PIPELINE);
|
||||
$pipe->lrem($this->getKeyForClaimed(), $item->qid, -1);
|
||||
$pipe->lpush($this->getKeyForQueue(), $item->qid);
|
||||
$ret = $pipe->exec();
|
||||
|
||||
return $ret[0] && $ret[1];
|
||||
}
|
||||
|
||||
public function createQueue()
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteQueue()
|
||||
{
|
||||
$this->getClient()->del(
|
||||
$this->getKeyForQueue(),
|
||||
$this->getKeyForClaimed(),
|
||||
$this->getKeyForData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_AbstractUnitTestCase extends DrupalUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
static protected $loaderEnabled = false;
|
||||
|
||||
/**
|
||||
* Enable the autoloader
|
||||
*
|
||||
* This exists in this class in case the autoloader is not set into the
|
||||
* settings.php file or another way
|
||||
*
|
||||
* @return void|boolean
|
||||
*/
|
||||
static protected function enableAutoload()
|
||||
{
|
||||
if (self::$loaderEnabled) {
|
||||
return;
|
||||
}
|
||||
if (class_exists('Redis_Client')) {
|
||||
return;
|
||||
}
|
||||
|
||||
spl_autoload_register(function ($className) {
|
||||
$parts = explode('_', $className);
|
||||
if ('Redis' === $parts[0]) {
|
||||
$filename = __DIR__ . '/../lib/' . implode('/', $parts) . '.php';
|
||||
return (bool) include_once $filename;
|
||||
}
|
||||
return false;
|
||||
}, null, true);
|
||||
|
||||
self::$loaderEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal $conf array backup
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $originalConf = array(
|
||||
'cache_lifetime' => null,
|
||||
'cache_prefix' => null,
|
||||
'redis_client_interface' => null,
|
||||
'redis_eval_enabled' => null,
|
||||
'redis_flush_mode' => null,
|
||||
'redis_perm_ttl' => null,
|
||||
);
|
||||
|
||||
/**
|
||||
* Prepare Drupal environmment for testing
|
||||
*/
|
||||
final private function prepareDrupalEnvironment()
|
||||
{
|
||||
// Site on which the tests are running may define this variable
|
||||
// in their own settings.php file case in which it will be merged
|
||||
// with testing site
|
||||
global $conf;
|
||||
foreach (array_keys($this->originalConf) as $key) {
|
||||
if (isset($conf[$key])) {
|
||||
$this->originalConf[$key] = $conf[$key];
|
||||
unset($conf[$key]);
|
||||
}
|
||||
}
|
||||
$conf['cache_prefix'] = $this->testId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore Drupal environment after testing.
|
||||
*/
|
||||
final private function restoreDrupalEnvironment()
|
||||
{
|
||||
$GLOBALS['conf'] = $this->originalConf + $GLOBALS['conf'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare client manager
|
||||
*/
|
||||
final private function prepareClientManager()
|
||||
{
|
||||
$interface = $this->getClientInterface();
|
||||
|
||||
if (null === $interface) {
|
||||
throw new \Exception("Test skipped due to missing driver");
|
||||
}
|
||||
|
||||
$GLOBALS['conf']['redis_client_interface'] = $interface;
|
||||
Redis_Client::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore client manager
|
||||
*/
|
||||
final private function restoreClientManager()
|
||||
{
|
||||
Redis_Client::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the Redis configuration.
|
||||
*
|
||||
* Set up the needed variables using variable_set() if necessary.
|
||||
*
|
||||
* @return string
|
||||
* Client interface or null if not exists
|
||||
*/
|
||||
abstract protected function getClientInterface();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp()
|
||||
{
|
||||
self::enableAutoload();
|
||||
|
||||
$this->prepareDrupalEnvironment();
|
||||
$this->prepareClientManager();
|
||||
|
||||
parent::setUp();
|
||||
|
||||
drupal_install_schema('system');
|
||||
drupal_install_schema('locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function tearDown()
|
||||
{
|
||||
drupal_uninstall_schema('locale');
|
||||
drupal_uninstall_schema('system');
|
||||
|
||||
$this->restoreDrupalEnvironment();
|
||||
$this->restoreClientManager();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Admin_VariableTestCase extends DrupalWebTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Redis variables',
|
||||
'description' => 'Checks that Redis module variables are correctly type hinted when saved.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected $adminUser;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp('redis');
|
||||
}
|
||||
|
||||
public function testSave()
|
||||
{
|
||||
$this->adminUser = $this->drupalCreateUser(array('administer site configuration'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Tests port is an int.
|
||||
$this->drupalGet('admin/config/development/performance/redis');
|
||||
$edit = array(
|
||||
'redis_client_base' => '',
|
||||
'redis_client_port' => '1234',
|
||||
'redis_client_host' => 'localhost',
|
||||
'redis_client_interface' => '',
|
||||
);
|
||||
$this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration'));
|
||||
|
||||
// Force variable cache to refresh.
|
||||
$test = variable_initialize();
|
||||
$conf = &$GLOBALS['conf'];
|
||||
|
||||
$this->assertFalse(array_key_exists('redis_client_base', $conf), "Empty int value has been removed");
|
||||
$this->assertFalse(array_key_exists('redis_client_interface', $conf), "Empty string value has been removed");
|
||||
$this->assertIdentical($conf['redis_client_port'], 1234, "Saved int is an int");
|
||||
$this->assertIdentical($conf['redis_client_host'], 'localhost', "Saved string is a string");
|
||||
|
||||
$this->drupalGet('admin/config/development/performance/redis');
|
||||
$edit = array(
|
||||
'redis_client_base' => '0',
|
||||
'redis_client_port' => '1234',
|
||||
'redis_client_host' => 'localhost',
|
||||
'redis_client_interface' => '',
|
||||
);
|
||||
$this->drupalPost('admin/config/development/performance/redis', $edit, t('Save configuration'));
|
||||
|
||||
// Force variable cache to refresh.
|
||||
$test = variable_initialize();
|
||||
$conf = &$GLOBALS['conf'];
|
||||
|
||||
$this->assertIdentical($conf['redis_client_base'], 0, "Saved 0 valueed int is an int");
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FixesUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FlushUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_CompressedPhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Compressed PhpRedis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_CacheCompressed($name);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_AbstractUnitTestCase')) {
|
||||
require_once(__DIR__ . '/../AbstractUnitTestCase.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Bugfixes made over time test class.
|
||||
*/
|
||||
abstract class Redis_Tests_Cache_FixesUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_Cache($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Cache
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$backend = $this->createCacheInstance($name);
|
||||
|
||||
$this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline");
|
||||
|
||||
return $backend;
|
||||
}
|
||||
|
||||
public function testTemporaryCacheExpire()
|
||||
{
|
||||
global $conf; // We are in unit tests so variable table does not exist.
|
||||
|
||||
$backend = $this->getBackend();
|
||||
|
||||
// Permanent entry.
|
||||
$backend->set('test1', 'foo', CACHE_PERMANENT);
|
||||
$data = $backend->get('test1');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foo', $data->data);
|
||||
|
||||
// Permanent entries should not be dropped on clear() call.
|
||||
$backend->clear();
|
||||
$data = $backend->get('test1');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foo', $data->data);
|
||||
|
||||
// Expiring entry with permanent default lifetime.
|
||||
$conf['cache_lifetime'] = 0;
|
||||
$backend->set('test2', 'bar', CACHE_TEMPORARY);
|
||||
sleep(2);
|
||||
$data = $backend->get('test2');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('bar', $data->data);
|
||||
sleep(2);
|
||||
$data = $backend->get('test2');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('bar', $data->data);
|
||||
|
||||
// Expiring entry with negative lifetime.
|
||||
$backend->set('test3', 'baz', time() - 100);
|
||||
$data = $backend->get('test3');
|
||||
$this->assertEqual(false, $data);
|
||||
|
||||
// Expiring entry with short lifetime.
|
||||
$backend->set('test4', 'foobar', time() + 2);
|
||||
$data = $backend->get('test4');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foobar', $data->data);
|
||||
sleep(4);
|
||||
$data = $backend->get('test4');
|
||||
$this->assertEqual(false, $data);
|
||||
|
||||
// Expiring entry with short default lifetime.
|
||||
$conf['cache_lifetime'] = 1;
|
||||
$backend->refreshMaxTtl();
|
||||
$backend->set('test5', 'foobaz', CACHE_TEMPORARY);
|
||||
$data = $backend->get('test5');
|
||||
$this->assertNotEqual(false, $data);
|
||||
$this->assertIdentical('foobaz', $data->data);
|
||||
sleep(3);
|
||||
$data = $backend->get('test5');
|
||||
$this->assertEqual(false, $data);
|
||||
}
|
||||
|
||||
public function testDefaultPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
unset($conf['redis_perm_ttl']);
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(Redis_Cache::LIFETIME_PERM_DEFAULT, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testUserSetDefaultPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "3 months";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(7776000, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testUserSetPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "1 months";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(2592000, $backend->getPermTtl());
|
||||
}
|
||||
|
||||
public function testGetMultiple()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('multiple1', 1);
|
||||
$backend->set('multiple2', 2);
|
||||
$backend->set('multiple3', 3);
|
||||
$backend->set('multiple4', 4);
|
||||
|
||||
$cidList = array('multiple1', 'multiple2', 'multiple3', 'multiple4', 'multiple5');
|
||||
$ret = $backend->getMultiple($cidList);
|
||||
|
||||
$this->assertEqual(1, count($cidList));
|
||||
$this->assertFalse(isset($cidList[0]));
|
||||
$this->assertFalse(isset($cidList[1]));
|
||||
$this->assertFalse(isset($cidList[2]));
|
||||
$this->assertFalse(isset($cidList[3]));
|
||||
$this->assertTrue(isset($cidList[4]));
|
||||
|
||||
$this->assertEqual(4, count($ret));
|
||||
$this->assertTrue(isset($ret['multiple1']));
|
||||
$this->assertTrue(isset($ret['multiple2']));
|
||||
$this->assertTrue(isset($ret['multiple3']));
|
||||
$this->assertTrue(isset($ret['multiple4']));
|
||||
$this->assertFalse(isset($ret['multiple5']));
|
||||
}
|
||||
|
||||
public function testPermTtl()
|
||||
{
|
||||
global $conf;
|
||||
// This also testes string parsing. Not fully, but at least one case.
|
||||
$conf['redis_perm_ttl'] = "2 seconds";
|
||||
$backend = $this->getBackend();
|
||||
$this->assertIdentical(2, $backend->getPermTtl());
|
||||
|
||||
$backend->set('test6', 'cats are mean');
|
||||
$this->assertIdentical('cats are mean', $backend->get('test6')->data);
|
||||
|
||||
sleep(3);
|
||||
$item = $backend->get('test6');
|
||||
$this->assertTrue(empty($item));
|
||||
}
|
||||
|
||||
public function testClearAsArray()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('test7', 1);
|
||||
$backend->set('test8', 2);
|
||||
$backend->set('test9', 3);
|
||||
|
||||
$backend->clear(array('test7', 'test9'));
|
||||
|
||||
$item = $backend->get('test7');
|
||||
$this->assertTrue(empty($item));
|
||||
$item = $backend->get('test8');
|
||||
$this->assertEqual(2, $item->data);
|
||||
$item = $backend->get('test9');
|
||||
$this->assertTrue(empty($item));
|
||||
}
|
||||
|
||||
public function testGetMultipleAlterCidsWhenCacheHitsOnly()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
$backend->clear('*', true); // It seems that there are leftovers.
|
||||
|
||||
$backend->set('mtest1', 'pouf');
|
||||
|
||||
$cids_partial_hit = array('foo' => 'mtest1', 'bar' => 'mtest2');
|
||||
$entries = $backend->getMultiple($cids_partial_hit);
|
||||
$this->assertIdentical(1, count($entries));
|
||||
// Note that the key is important because the method should
|
||||
// keep the keys synchronized.
|
||||
$this->assertEqual(array('bar' => 'mtest2'), $cids_partial_hit);
|
||||
|
||||
$backend->clear('mtest1');
|
||||
|
||||
$cids_no_hit = array('cat' => 'mtest1', 'dog' => 'mtest2');
|
||||
$entries = $backend->getMultiple($cids_no_hit);
|
||||
$this->assertIdentical(0, count($entries));
|
||||
$this->assertEqual(array('cat' => 'mtest1', 'dog' => 'mtest2'), $cids_no_hit);
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_Cache_FlushUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
protected function createCacheInstance($name = null)
|
||||
{
|
||||
return new Redis_Cache($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Cache
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$backend = $this->createCacheInstance($name);
|
||||
|
||||
$this->assert(true, "Redis client is " . ($backend->isSharded() ? '' : "NOT ") . " sharded");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowTemporaryFlush() ? '' : "NOT ") . " allowed to flush temporary entries");
|
||||
$this->assert(true, "Redis client is " . ($backend->allowPipeline() ? '' : "NOT ") . " allowed to use pipeline");
|
||||
|
||||
return $backend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that with a default cache lifetime temporary non expired
|
||||
* items are kept even when in temporary flush mode.
|
||||
*/
|
||||
public function testFlushIsTemporaryWithLifetime()
|
||||
{
|
||||
$GLOBALS['conf']['cache_lifetime'] = 112;
|
||||
|
||||
$backend = $this->getBackend();
|
||||
|
||||
// Even though we set a flush mode into this bin, Drupal default
|
||||
// behavior when a cache_lifetime is set is to override the backend
|
||||
// one in order to keep the core behavior and avoid potential
|
||||
// nasty bugs.
|
||||
$this->assertFalse($backend->allowTemporaryFlush());
|
||||
|
||||
$backend->set('test7', 42, CACHE_PERMANENT);
|
||||
$backend->set('test8', 'foo', CACHE_TEMPORARY);
|
||||
$backend->set('test9', 'bar', time() + 1000);
|
||||
|
||||
$backend->clear();
|
||||
|
||||
$cache = $backend->get('test7');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 42);
|
||||
$cache = $backend->get('test8');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 'foo');
|
||||
$cache = $backend->get('test9');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 'bar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that with no default cache lifetime all temporary items are
|
||||
* droppped when in temporary flush mode.
|
||||
*/
|
||||
public function testFlushIsTemporaryWithoutLifetime()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$this->assertTrue($backend->allowTemporaryFlush());
|
||||
|
||||
$backend->set('test10', 42, CACHE_PERMANENT);
|
||||
// Ugly concatenation with the mode, but it will be visible in tests
|
||||
// reports if the entry shows up, thus allowing us to know which real
|
||||
// test case is run at this time
|
||||
$backend->set('test11', 'foo' . $backend->isSharded(), CACHE_TEMPORARY);
|
||||
$backend->set('test12', 'bar' . $backend->isSharded(), time() + 10);
|
||||
|
||||
$backend->clear();
|
||||
|
||||
$cache = $backend->get('test10');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
$this->assertEqual($cache->data, 42);
|
||||
$this->assertFalse($backend->get('test11'));
|
||||
|
||||
$cache = $backend->get('test12');
|
||||
$this->assertNotEqual(false, $cache);
|
||||
}
|
||||
|
||||
public function testNormalFlushing()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
$backendUntouched = $this->getBackend();
|
||||
|
||||
// Set a few entries.
|
||||
$backend->set('test13', 'foo');
|
||||
$backend->set('test14', 'bar', CACHE_TEMPORARY);
|
||||
$backend->set('test15', 'baz', time() + 3);
|
||||
|
||||
$backendUntouched->set('test16', 'dog');
|
||||
$backendUntouched->set('test17', 'cat', CACHE_TEMPORARY);
|
||||
$backendUntouched->set('test18', 'xor', time() + 5);
|
||||
|
||||
// This should not do anything (bugguy command)
|
||||
$backend->clear('', true);
|
||||
$backend->clear('', false);
|
||||
$this->assertNotIdentical(false, $backend->get('test13'));
|
||||
$this->assertNotIdentical(false, $backend->get('test14'));
|
||||
$this->assertNotIdentical(false, $backend->get('test15'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test16'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test17'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test18'));
|
||||
|
||||
// This should clear every one, permanent and volatile
|
||||
$backend->clear('*', true);
|
||||
$this->assertFalse($backend->get('test13'));
|
||||
$this->assertFalse($backend->get('test14'));
|
||||
$this->assertFalse($backend->get('test15'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test16'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test17'));
|
||||
$this->assertNotIdentical(false, $backendUntouched->get('test18'));
|
||||
}
|
||||
|
||||
public function testPrefixDeletionWithSeparatorChar()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->set('testprefix10', 'foo');
|
||||
$backend->set('testprefix11', 'foo');
|
||||
$backend->set('testprefix:12', 'bar');
|
||||
$backend->set('testprefix:13', 'baz');
|
||||
$backend->set('testnoprefix14', 'giraffe');
|
||||
$backend->set('testnoprefix:15', 'elephant');
|
||||
|
||||
$backend->clear('testprefix:', true);
|
||||
$this->assertFalse($backend->get('testprefix:12'));
|
||||
$this->assertFalse($backend->get('testprefix:13'));
|
||||
// @todo Temporary fix
|
||||
// At the moment shard enabled backends will erase all data instead
|
||||
// of just removing by prefix, so those tests won't pass
|
||||
if (!$backend->isSharded()) {
|
||||
$this->assertNotIdentical(false, $backend->get('testprefix10'));
|
||||
$this->assertNotIdentical(false, $backend->get('testprefix11'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix14'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix:15'));
|
||||
}
|
||||
|
||||
$backend->clear('testprefix', true);
|
||||
$this->assertFalse($backend->get('testprefix10'));
|
||||
$this->assertFalse($backend->get('testprefix11'));
|
||||
// @todo Temporary fix
|
||||
// At the moment shard enabled backends will erase all data instead
|
||||
// of just removing by prefix, so those tests won't pass
|
||||
if (!$backend->isSharded()) {
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix14'));
|
||||
$this->assertNotIdentical(false, $backend->get('testnoprefix:15'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testOrder()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
$id = 'speedtest' . $i;
|
||||
$backend->set($id, 'somevalue');
|
||||
$this->assertNotIdentical(false, $backend->get($id));
|
||||
$backend->clear('*', true);
|
||||
// Value created the same second before is dropped
|
||||
$this->assertFalse($backend->get($id));
|
||||
$backend->set($id, 'somevalue');
|
||||
// Value created the same second after is kept
|
||||
$this->assertNotIdentical(false, $backend->get($id));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FixesUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FixesUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Cache_FlushUnitTestCase')) {
|
||||
require_once(__DIR__ . '/FlushUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PhpRedisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache flush',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes (S)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedFlushUnitTestCase extends Redis_Tests_Cache_FlushUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache flush (S)',
|
||||
'description' => 'Tests Redis module cache flush modes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Cache_PredisShardedWithPipelineFixesUnitTestCase extends Redis_Tests_Cache_FixesUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis cache fixes (SP)',
|
||||
'description' => 'Tests Redis module cache fixes feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
$GLOBALS['conf']['redis_flush_mode'] = Redis_Cache::FLUSH_SHARD_WITH_PIPELINING;
|
||||
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Client_UnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Redis client manager',
|
||||
'description' => 'Tests Redis module client manager feature.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
|
||||
public function getManager()
|
||||
{
|
||||
return new Redis_Client_Manager(
|
||||
new Redis_Tests_Client_MockFactory(),
|
||||
array(
|
||||
'default' => array(),
|
||||
'foo' => array(
|
||||
'host' => 'foo.com',
|
||||
'port' => 666,
|
||||
),
|
||||
'bar' => array(
|
||||
'host' => 'bar.com',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testManagerServerList()
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
$defaultClient = $manager->getClient();
|
||||
$this->assertTrue(is_object($defaultClient));
|
||||
|
||||
// Ensure defaults are OK
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_HOST, $defaultClient->host);
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $defaultClient->port);
|
||||
$this->assertFalse(property_exists($defaultClient, 'base'));
|
||||
$this->assertFalse(property_exists($defaultClient, 'password'));
|
||||
|
||||
$client = $manager->getClient('foo');
|
||||
$this->assertIdentical('foo.com', $client->host);
|
||||
$this->assertIdentical(666, $client->port);
|
||||
|
||||
$client = $manager->getClient('bar');
|
||||
$this->assertIdentical('bar.com', $client->host);
|
||||
$this->assertIdentical(Redis_Client_Manager::REDIS_DEFAULT_PORT, $client->port);
|
||||
|
||||
$this->assertIdentical($defaultClient, $manager->getClient('non_existing'));
|
||||
|
||||
try {
|
||||
$manager->getClient('other_non_existing', false);
|
||||
$this->assert(false);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->assert(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Client_MockFactory implements Redis_Client_FactoryInterface
|
||||
{
|
||||
public function getClient($options = array())
|
||||
{
|
||||
return (object)$options;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'Mock';
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
abstract class Redis_Tests_Lock_LockingUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* Ensure lock flush at tear down
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $backends = array();
|
||||
|
||||
/**
|
||||
* Get the lock client class name
|
||||
*
|
||||
* @return string
|
||||
* Lock backend class name or null if cannot spawn it
|
||||
*/
|
||||
abstract protected function getLockBackendClass();
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
if (!empty($this->backends)) {
|
||||
foreach ($this->backends as $backend) {
|
||||
$backend->lockReleaseAll();
|
||||
}
|
||||
|
||||
$this->backends = array();
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new lock backend with a generated lock id
|
||||
*
|
||||
* @return Redis_Lock_BackendInterface
|
||||
*/
|
||||
public function createLockBackend()
|
||||
{
|
||||
if (!$this->getLockBackendClass()) {
|
||||
throw new \Exception("Lock backend class does not exist");
|
||||
}
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_LOCK);
|
||||
|
||||
return $this->backends[] = new $className(
|
||||
Redis_Client::getClient(),
|
||||
Redis_Client::getDefaultPrefix('lock')
|
||||
);
|
||||
}
|
||||
|
||||
public function testLock()
|
||||
{
|
||||
$b1 = $this->createLockBackend();
|
||||
$b2 = $this->createLockBackend();
|
||||
|
||||
$s = $b1->lockAcquire('test1', 20000);
|
||||
$this->assertTrue($s, "Lock test1 acquired");
|
||||
|
||||
$s = $b1->lockAcquire('test1', 20000);
|
||||
$this->assertTrue($s, "Lock test1 acquired a second time by the same thread");
|
||||
|
||||
$s = $b2->lockAcquire('test1', 20000);
|
||||
$this->assertFalse($s, "Lock test1 could not be acquired by another thread");
|
||||
|
||||
$b2->lockRelease('test1');
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertFalse($s, "Lock test1 could not be released by another thread");
|
||||
|
||||
$b1->lockRelease('test1');
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertTrue($s, "Lock test1 has been released by the first thread");
|
||||
}
|
||||
|
||||
public function testReleaseAll()
|
||||
{
|
||||
$b1 = $this->createLockBackend();
|
||||
$b2 = $this->createLockBackend();
|
||||
|
||||
$b1->lockAcquire('test1', 200);
|
||||
$b1->lockAcquire('test2', 2000);
|
||||
$b1->lockAcquire('test3', 20000);
|
||||
|
||||
$s = $b2->lockAcquire('test2');
|
||||
$this->assertFalse($s, "Lock test2 could not be released by another thread");
|
||||
$s = $b2->lockAcquire('test3');
|
||||
$this->assertFalse($s, "Lock test4 could not be released by another thread");
|
||||
|
||||
$b1->lockReleaseAll();
|
||||
|
||||
$s = $b2->lockAcquire('test1');
|
||||
$this->assertTrue($s, "Lock test1 has been released");
|
||||
$s = $b2->lockAcquire('test2');
|
||||
$this->assertTrue($s, "Lock test2 has been released");
|
||||
$s = $b2->lockAcquire('test3');
|
||||
$this->assertTrue($s, "Lock test3 has been released");
|
||||
|
||||
$b2->lockReleaseAll();
|
||||
}
|
||||
|
||||
public function testConcurentLock()
|
||||
{
|
||||
/*
|
||||
* Code for web test case
|
||||
*
|
||||
$this->drupalGet('redis/acquire/test1/1000');
|
||||
$this->assertText("REDIS_ACQUIRED", "Lock test1 acquired");
|
||||
|
||||
$this->drupalGet('redis/acquire/test1/1');
|
||||
$this->assertText("REDIS_FAILED", "Lock test1 could not be acquired by a second thread");
|
||||
|
||||
$this->drupalGet('redis/acquire/test2/1000');
|
||||
$this->assertText("REDIS_ACQUIRED", "Lock test2 acquired");
|
||||
|
||||
$this->drupalGet('redis/acquire/test2/1');
|
||||
$this->assertText("REDIS_FAILED", "Lock test2 could not be acquired by a second thread");
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Lock_LockingUnitTestCase')) {
|
||||
require_once(__DIR__ . '/LockingUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Lock_PhpRedisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis Redis locking',
|
||||
'description' => 'Ensure that Redis locking feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLockBackendClass()
|
||||
{
|
||||
return 'Redis_Lock_PhpRedis';
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Lock_PredisLockingUnitTestCase extends Redis_Tests_Lock_LockingUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis Redis locking',
|
||||
'description' => 'Ensure that Redis locking feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLockBackendClass()
|
||||
{
|
||||
return 'Redis_Lock_Predis';
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Bugfixes made over time test class.
|
||||
*/
|
||||
abstract class Redis_Tests_Path_PathUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Cache bin identifier
|
||||
*/
|
||||
static private $id = 1;
|
||||
|
||||
/**
|
||||
* Get cache backend
|
||||
*
|
||||
* @return Redis_Path_HashLookupInterface
|
||||
*/
|
||||
final protected function getBackend($name = null)
|
||||
{
|
||||
if (null === $name) {
|
||||
// This is needed to avoid conflict between tests, each test
|
||||
// seems to use the same Redis namespace and conflicts are
|
||||
// possible.
|
||||
$name = 'cache' . (self::$id++);
|
||||
}
|
||||
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_PATH);
|
||||
$hashLookup = new $className(Redis_Client::getClient(), 'path', Redis_Client::getDefaultPrefix('path'));
|
||||
|
||||
return $hashLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic functionnality
|
||||
*/
|
||||
public function testPathLookup()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
|
||||
$backend->saveAlias('node/1', 'node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr', $alias);
|
||||
|
||||
// Delete and ensure it does not exist anymore.
|
||||
$backend->deleteAlias('node/1', 'node-1-fr', 'fr');
|
||||
$source = $backend->lookupSource('node-1-fr', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
|
||||
// Set more than one aliases and ensure order at loading.
|
||||
$backend->saveAlias('node/1', 'node-1-fr-1', 'fr');
|
||||
$backend->saveAlias('node/1', 'node-1-fr-2', 'fr');
|
||||
$backend->saveAlias('node/1', 'node-1-fr-3', 'fr');
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr-3', $alias);
|
||||
|
||||
// Add another alias to test the delete language feature.
|
||||
// Also add some other languages aliases.
|
||||
$backend->saveAlias('node/1', 'node-1');
|
||||
$backend->saveAlias('node/2', 'node-2-en', 'en');
|
||||
$backend->saveAlias('node/3', 'node-3-ca', 'ca');
|
||||
|
||||
// Ok, delete fr and tests every other are still there.
|
||||
$backend->deleteLanguage('fr');
|
||||
$alias = $backend->lookupAlias('node/1');
|
||||
$this->assertIdentical('node-1', $alias);
|
||||
$alias = $backend->lookupAlias('node/2', 'en');
|
||||
$this->assertIdentical('node-2-en', $alias);
|
||||
$alias = $backend->lookupAlias('node/3', 'ca');
|
||||
$this->assertIdentical('node-3-ca', $alias);
|
||||
|
||||
// Now create back a few entries in some langage and
|
||||
// ensure fallback to no language also works.
|
||||
$backend->saveAlias('node/4', 'node-4');
|
||||
$backend->saveAlias('node/4', 'node-4-es', 'es');
|
||||
$alias = $backend->lookupAlias('node/4');
|
||||
$this->assertIdentical('node-4', $alias);
|
||||
$alias = $backend->lookupAlias('node/4', 'es');
|
||||
$this->assertIdentical('node-4-es', $alias);
|
||||
$alias = $backend->lookupAlias('node/4', 'fr');
|
||||
$this->assertIdentical('node-4', $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests https://www.drupal.org/node/2728831
|
||||
*/
|
||||
public function testSomeEdgeCaseFalseNegative()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->deleteLanguage('fr');
|
||||
$backend->deleteLanguage('und');
|
||||
$backend->saveAlias('node/123', 'node-123');
|
||||
|
||||
// Language lookup should return the language neutral value if no value
|
||||
$source = $backend->lookupSource('node-123', 'fr');
|
||||
$this->assertIdentical($source, 'node/123');
|
||||
$source = $backend->lookupAlias('node/123', 'fr');
|
||||
$this->assertIdentical($source, 'node-123');
|
||||
|
||||
// Now, let's consider we have an item we don't know if it exists or
|
||||
// not, per definition we should not return a strict FALSE but a NULL
|
||||
// value instead to tell "we don't know anything about this". In a
|
||||
// very specific use-case, if the language neutral value is a strict
|
||||
// "not exists" value, it should still return NULL instead of FALSE
|
||||
// if another language was asked for.
|
||||
|
||||
// Store "value null" for the language neutral entry
|
||||
$backend->saveAlias('node/456', Redis_Path_HashLookupInterface::VALUE_NULL);
|
||||
$source = $backend->lookupAlias('node/456');
|
||||
$this->assertIdentical(false, $source);
|
||||
|
||||
$source = $backend->lookupAlias('node/456', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that lookup is case insensitive
|
||||
*/
|
||||
public function testCaseInsensitivePathLookup()
|
||||
{
|
||||
$backend = $this->getBackend();
|
||||
|
||||
$backend->saveAlias('node/1', 'Node-1-FR', 'fr');
|
||||
$source = $backend->lookupSource('NODE-1-fr', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$source = $backend->lookupSource('node-1-FR', 'fr');
|
||||
$this->assertIdentical('node/1', $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical('node-1-fr', strtolower($alias));
|
||||
|
||||
// Delete and ensure it does not exist anymore.
|
||||
$backend->deleteAlias('node/1', 'node-1-FR', 'fr');
|
||||
$source = $backend->lookupSource('Node-1-FR', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
$alias = $backend->lookupAlias('node/1', 'fr');
|
||||
$this->assertIdentical(null, $source);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Path_PathUnitTestCase')) {
|
||||
require_once(__DIR__ . '/PathUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Path_PhpRedisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis path inc replacement',
|
||||
'description' => 'Tests PhpRedis path inc replacement.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Redis_Tests_Path_PredisPathUnitTestCase extends Redis_Tests_Path_PathUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis path inc replacement',
|
||||
'description' => 'Tests Predis path inc replacement.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('Redis_Tests_Queue_QueueUnitTestCase')) {
|
||||
require_once(__DIR__ . '/QueueUnitTestCase.php');
|
||||
}
|
||||
|
||||
class Redis_Tests_Queue_PhpRedisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'PhpRedis Redis queue',
|
||||
'description' => 'Ensure that Redis queue feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'PhpRedis';
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
class Redis_Tests_Queue_PredisQueueUnitTestCase extends Redis_Tests_Queue_QueueUnitTestCase
|
||||
{
|
||||
public static function getInfo()
|
||||
{
|
||||
return array(
|
||||
'name' => 'Predis Redis queue',
|
||||
'description' => 'Ensure that Redis queue feature is working OK.',
|
||||
'group' => 'Redis',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClientInterface()
|
||||
{
|
||||
return 'Predis';
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Some tests in there credits goes to the redis_queue module.
|
||||
* Thanks to their author.
|
||||
*/
|
||||
abstract class Redis_Tests_Queue_QueueUnitTestCase extends Redis_Tests_AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @var Redis_Queue
|
||||
*/
|
||||
public $queue;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
module_load_include('inc', 'system', 'system.queue');
|
||||
|
||||
$this->name = 'redis-queue-test-' . time();
|
||||
$this->queue = new Redis_Queue($this->name);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
$this->queue->deleteQueue();
|
||||
$this->name = null;
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testCreate()
|
||||
{
|
||||
$res = $this->queue->createItem('test-queue-item-create');
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(1, $num);
|
||||
}
|
||||
|
||||
public function testClaim()
|
||||
{
|
||||
$data = 'test-queue-item-claimed';
|
||||
$res = $this->queue->createItem($data);
|
||||
$item = $this->queue->claimItem();
|
||||
$this->assertEqual($data, $item->data);
|
||||
}
|
||||
|
||||
/*
|
||||
public function testClaimBlocking()
|
||||
{
|
||||
$data = 'test-queue-item-claimed';
|
||||
$res = $this->queue->createItem($data);
|
||||
$this->assertTrue($res);
|
||||
$item = $this->queue->claimItemBlocking(10);
|
||||
$this->assertEqual($data, $item->data);
|
||||
}
|
||||
*/
|
||||
|
||||
public function testRelease()
|
||||
{
|
||||
$data = 'test-queue-item';
|
||||
|
||||
$res = $this->queue->createItem($data);
|
||||
$item = $this->queue->claimItem();
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(0, $num);
|
||||
|
||||
$res = $this->queue->releaseItem($item);
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(1, $num);
|
||||
}
|
||||
|
||||
public function testOrder()
|
||||
{
|
||||
$keys = array('test1', 'test2', 'test3');
|
||||
foreach ($keys as $k) {
|
||||
$this->queue->createItem($k);
|
||||
}
|
||||
|
||||
$first = $this->queue->claimItem();
|
||||
$this->assertEqual($first->data, $keys[0]);
|
||||
|
||||
$second = $this->queue->claimItem();
|
||||
$this->assertEqual($second->data, $keys[1]);
|
||||
|
||||
$this->queue->releaseItem($first);
|
||||
|
||||
$third = $this->queue->claimItem();
|
||||
$this->assertEqual($third->data, $keys[2]);
|
||||
|
||||
$first_again = $this->queue->claimItem();
|
||||
$this->assertEqual($first_again->data, $keys[0]);
|
||||
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEqual(0, $num);
|
||||
}
|
||||
|
||||
/*
|
||||
public function lease()
|
||||
{
|
||||
$data = 'test-queue-item';
|
||||
$res = $this->queue->createItem($data);
|
||||
$num = $this->queue->numberOfItems();
|
||||
$this->assertEquals(1, $num);
|
||||
$item = $this->queue->claimItem(1);
|
||||
// In Redis 2.4 the expire could be between zero to one seconds off.
|
||||
sleep(2);
|
||||
$expired = $this->queue->expire();
|
||||
$this->assertEquals(1, $expired);
|
||||
$this->assertEquals(1, $this->queue->numberOfItems());
|
||||
// Create a second queue to test expireAll()
|
||||
$q2 = new RedisQueue($this->name . '_2');
|
||||
$q2->createItem($data);
|
||||
$q2->createItem($data);
|
||||
$this->assertEquals(2, $q2->numberOfItems());
|
||||
$item = $this->queue->claimItem(1);
|
||||
$item2 = $q2->claimItem(1);
|
||||
$this->assertEquals(1, $q2->numberOfItems());
|
||||
sleep(2);
|
||||
$expired = $this->queue->expireAll();
|
||||
$this->assertEquals(2, $expired);
|
||||
$this->assertEquals(1, $this->queue->numberOfItems());
|
||||
$this->assertEquals(2, $q2->numberOfItems());
|
||||
$q2->deleteQueue();
|
||||
}
|
||||
*/
|
||||
}
|
104
sites/all/modules/contrib/dev/redis/redis.admin.inc
Normal file
104
sites/all/modules/contrib/dev/redis/redis.admin.inc
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Redis module administration pages.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main settings and review administration screen.
|
||||
*/
|
||||
function redis_settings_form($form, &$form_state) {
|
||||
|
||||
$form['connection'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t("Connection information"),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
$form['connection']['scheme'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t("Scheme"),
|
||||
'#default_value' => 'tcp',
|
||||
'#disabled' => TRUE,
|
||||
'#description' => t("Connection scheme.") . " " . t("Only <em>tcp</em> is currently supported. This is ignored when using a UNIX socket."),
|
||||
);
|
||||
$form['connection']['redis_client_host'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t("Host"),
|
||||
'#default_value' => variable_get('redis_client_host', NULL),
|
||||
'#description' => t("Redis server host. Default is <em>@default</em>.", array('@default' => Redis_Client_Manager::REDIS_DEFAULT_HOST)),
|
||||
);
|
||||
$form['connection']['redis_client_port'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t("Port"),
|
||||
'#default_value' => variable_get('redis_client_port', NULL),
|
||||
'#description' => t("Redis server port. Default is <em>@default</em>.", array('@default' => Redis_Client_Manager::REDIS_DEFAULT_PORT)),
|
||||
);
|
||||
$form['connection']['redis_client_socket'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t("UNIX socket"),
|
||||
'#default_value' => variable_get('redis_client_socket', NULL),
|
||||
'#description' => t("Redis UNIX socket for connection. If set remote server host and port will be ignored."),
|
||||
);
|
||||
$form['connection']['redis_client_base'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t("Database"),
|
||||
'#default_value' => variable_get('redis_client_base', NULL),
|
||||
'#description' => t("Redis server database. Default is none, Redis server will autoselect the database 0."),
|
||||
);
|
||||
$form['connection']['redis_client_interface'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t("Client"),
|
||||
'#options' => array(
|
||||
NULL => t("None or automatic"),
|
||||
'PhpRedis' => t("PhpRedis PHP extension"),
|
||||
'Predis' => t("Predis PHP library"),
|
||||
),
|
||||
'#default_value' => variable_get('redis_client_interface', NULL),
|
||||
'#description' => t("Redis low level backend."),
|
||||
);
|
||||
|
||||
$form = system_settings_form($form);
|
||||
|
||||
// Enforce empty values drop from the $form_state in order to avoid empty
|
||||
// values saving. Empty values would cause the isset() checks in client
|
||||
// options to see false positives and fail upon connection.
|
||||
array_unshift($form['#submit'], 'redis_settings_form_submit_clean_values');
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep clean of $form_state values.
|
||||
*/
|
||||
function redis_settings_form_submit_clean_values($form, &$form_state) {
|
||||
|
||||
$string_values = array('redis_client_host', 'redis_client_interface');
|
||||
|
||||
foreach ($string_values as $name) {
|
||||
// Empty check is sufficient to verify that the field is indeed empty.
|
||||
if (empty($form_state['values'][$name])) {
|
||||
// Using unset() will keep the key in the array, with an associated NULL
|
||||
// value. While this wouldn't really matter, it's safer to remove it so
|
||||
// that system_settings_form_submit() won't find it and attempt to save
|
||||
// it.
|
||||
$form_state['values'] = array_diff_key($form_state['values'], array($name => NULL));
|
||||
variable_del($name);
|
||||
}
|
||||
}
|
||||
|
||||
$numeric_values = array('redis_client_base', 'redis_client_port');
|
||||
|
||||
foreach ($numeric_values as $name) {
|
||||
// Numeric values can be both of NULL or 0 (NULL meaning the value is not
|
||||
// not set and the client will use the default, while 0 has a business
|
||||
// meaning and should be kept as is).
|
||||
if ('0' !== $form_state['values'][$name] && empty($form_state['values'][$name])) {
|
||||
$form_state['values'] = array_diff_key($form_state['values'], array($name => NULL));
|
||||
variable_del($name);
|
||||
} else {
|
||||
$form_state['values'][$name] = (int)$form_state['values'][$name];
|
||||
}
|
||||
}
|
||||
}
|
25
sites/all/modules/contrib/dev/redis/redis.autoload.inc
Normal file
25
sites/all/modules/contrib/dev/redis/redis.autoload.inc
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Redis module autoloader.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Autoloader micro optimization, work with constant as much as we can.
|
||||
*/
|
||||
define('REDIS_ROOT', dirname(__FILE__) . '/lib');
|
||||
|
||||
/**
|
||||
* Redis module specific autoloader, compatible with spl_register_autoload().
|
||||
*/
|
||||
function redis_autoload($class_name) {
|
||||
if ('Redis' === substr($class_name, 0, 5)) {
|
||||
$filename = REDIS_ROOT . '/' . str_replace('_', '/', $class_name) . '.php';
|
||||
return @include_once $filename;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Register our custom autoloader.
|
||||
spl_autoload_register('redis_autoload');
|
44
sites/all/modules/contrib/dev/redis/redis.info
Normal file
44
sites/all/modules/contrib/dev/redis/redis.info
Normal file
@ -0,0 +1,44 @@
|
||||
name = "Redis"
|
||||
description = "Provide a module placeholder, for using as dependency for module that needs Redis."
|
||||
package = Performance
|
||||
version = VERSION
|
||||
core = 7.x
|
||||
configure = admin/config/development/performance/redis
|
||||
; Drupal Simpletest cannot rely on a real autoloader
|
||||
files[] = lib/Redis/Tests/AbstractUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Admin/VariableTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/FixesUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Cache/FlushUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Cache/CompressedPhpRedisFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/CompressedPhpRedisFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/CompressedPhpRedisShardedWithPipelineFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PhpRedisFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PhpRedisFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PhpRedisShardedFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PhpRedisShardedFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PhpRedisShardedWithPipelineFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PredisFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PredisFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PredisShardedFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PredisShardedFlushUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Cache/PredisShardedWithPipelineFixesUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Client/ClientUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Client/MockFactory.php
|
||||
files[] = lib/Redis/Tests/Lock/LockingUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Lock/PhpRedisLockingUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Lock/PredisLockingUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Path/PathUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Path/PhpRedisPathUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Path/PredisPathUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Queue/QueueUnitTestCase.php
|
||||
files[] = lib/Redis/Tests/Queue/PhpRedisQueueUnitTestCase.test
|
||||
files[] = lib/Redis/Tests/Queue/PredisQueueUnitTestCase.test
|
||||
|
||||
; Information added by Drupal.org packaging script on 2017-12-22
|
||||
version = "7.x-3.17"
|
||||
core = "7.x"
|
||||
project = "redis"
|
||||
datestamp = "1513939095"
|
||||
|
38
sites/all/modules/contrib/dev/redis/redis.install
Normal file
38
sites/all/modules/contrib/dev/redis/redis.install
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Redis install related functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function redis_requirements($phase) {
|
||||
|
||||
// This module is configured via settings.php file. Using any other phase
|
||||
// than runtime to proceed to some consistency checks is useless.
|
||||
if ('runtime' !== $phase) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$requirements = array();
|
||||
|
||||
try {
|
||||
Redis_Client::getClient();
|
||||
$requirements['redis'] = array(
|
||||
'title' => "Redis",
|
||||
'value' => t("Connected, using the <em>@name</em> client.", array('@name' => Redis_Client::getClientInterfaceName())),
|
||||
'severity' => REQUIREMENT_OK,
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$requirements['redis'] = array(
|
||||
'title' => "Redis",
|
||||
'value' => t("Not connected."),
|
||||
'severity' => REQUIREMENT_WARNING,
|
||||
'description' => t("No Redis client connected. Please ensure that your Redis connection is working properly. If you are not using a Redis server connection you should disable this module."),
|
||||
);
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
63
sites/all/modules/contrib/dev/redis/redis.lock.inc
Normal file
63
sites/all/modules/contrib/dev/redis/redis.lock.inc
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Drupal core lock.inc replacement.
|
||||
*
|
||||
* Do not use this file directly, it will be included by the backend specific
|
||||
* implementation when added to settings.php file.
|
||||
*
|
||||
* See README.txt file for details.
|
||||
*/
|
||||
|
||||
// Include our own autoloader to ensure classes to be there.
|
||||
// We cannot rely on core in case of early bootstrap phases.
|
||||
require_once dirname(__FILE__) . '/redis.autoload.inc';
|
||||
|
||||
/**
|
||||
* Foo function, keeping it for API consistency (Drupal 7).
|
||||
*/
|
||||
function lock_initialize() {}
|
||||
|
||||
/**
|
||||
* Foo function, keeping it for API consistency (Drupal 6).
|
||||
*/
|
||||
function lock_init() {}
|
||||
|
||||
/**
|
||||
* Foo function, keeping it for API consistency.
|
||||
* Some insane people may actually use it.
|
||||
*/
|
||||
function _lock_id() {
|
||||
return Redis_Lock::getBackend()->getLockId();
|
||||
}
|
||||
|
||||
function lock_acquire($name, $timeout = 30.0) {
|
||||
return Redis_Lock::getBackend()->lockAcquire($name, $timeout);
|
||||
}
|
||||
|
||||
function lock_may_be_available($name) {
|
||||
return Redis_Lock::getBackend()->lockMayBeAvailable($name);
|
||||
}
|
||||
|
||||
function lock_wait($name, $delay = 30) {
|
||||
return Redis_Lock::getBackend()->lockWait($name, $delay);
|
||||
}
|
||||
|
||||
function lock_release($name) {
|
||||
return Redis_Lock::getBackend()->lockRelease($name);
|
||||
}
|
||||
|
||||
function lock_release_all($lock_id = NULL) {
|
||||
return Redis_Lock::getBackend()->lockReleaseAll($lock_id);
|
||||
}
|
||||
|
||||
// Since D6 doesn't have the drupal_register_shutdown_function
|
||||
// that is called in lib/Redis/Lock/Backend/Default.php define
|
||||
// the wrapper here.
|
||||
if (!function_exists('drupal_register_shutdown_function')) {
|
||||
function drupal_register_shutdown_function(){
|
||||
$args = func_get_args();
|
||||
call_user_func_array('register_shutdown_function', $args);
|
||||
}
|
||||
}
|
73
sites/all/modules/contrib/dev/redis/redis.module
Normal file
73
sites/all/modules/contrib/dev/redis/redis.module
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Redis module.
|
||||
*
|
||||
* This file is a placeholder for other modules that need the Redis client for
|
||||
* something else than lock and cache.
|
||||
*/
|
||||
|
||||
// Include our own autoloader to ensure classes to be there.
|
||||
// We cannot rely on core in case of early bootstrap phases.
|
||||
require_once dirname(__FILE__) . '/redis.autoload.inc';
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function redis_menu() {
|
||||
$items = array();
|
||||
$items['admin/config/development/performance/cache'] = array(
|
||||
'title' => "Cache",
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
$items['admin/config/development/performance/redis'] = array(
|
||||
'title' => "Redis",
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('redis_settings_form'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'redis.admin.inc',
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function redis_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/config/development/performance/redis':
|
||||
$messages =
|
||||
'<p>' . t("Redis module is optional if you are using only a cache or lock backend. The full API will be automatically loaded and its configuration will live into the <em>settings.php</em> file. If you access to this screen, it's probably because another contrib module needs it as a dependency for using the Redis client. If you didn't enabled such module, you are strongly advised to disable the Redis module on the module page.") . '</p>' .
|
||||
'<p>' . t("While Redis client configuration can be changed through the web, if you are using a cache or lock backend they must be set in the <em>settings.php</em> file. Once this done, any modification done using this form will be ignored, and real settings in use will be get at early bootstrap phase, before the configuration system is bootstrapped.") . '</p>';
|
||||
if (Redis_Client::getClient()) {
|
||||
$messages .= '<p><strong>' . t("Current connected client uses the <em>@name</em> library.", array('@name' => Redis_Client::getClientInterfaceName())) . '</strong></p>';
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis client for php-redis extension.
|
||||
*
|
||||
* @return \Redis
|
||||
*/
|
||||
function phpredis_client_get() {
|
||||
if ('PhpRedis' !== variable_get('redis_client_interface')) {
|
||||
throw new \LogicException("Redis is not configured to use the php-redis client");
|
||||
}
|
||||
return Redis_Client::getClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redis client for Predis library.
|
||||
*
|
||||
* @return \Predis\Client
|
||||
*/
|
||||
function predis_client_get() {
|
||||
if ('Predis' !== variable_get('redis_client_interface')) {
|
||||
throw new \LogicException("Redis is not configured to use the Predis client");
|
||||
}
|
||||
return Redis_Client::getClient();
|
||||
}
|
594
sites/all/modules/contrib/dev/redis/redis.path.inc
Normal file
594
sites/all/modules/contrib/dev/redis/redis.path.inc
Normal file
@ -0,0 +1,594 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Drupal default includes/path.inc file copy which only differs in:
|
||||
* - drupal_lookup_path() which is the only performance critical.
|
||||
* - path_*() functions for synchronization.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get Redis path lookup backend.
|
||||
*
|
||||
* @return Redis_Path_HashLookupInterface
|
||||
*/
|
||||
function redis_path_backend_get() {
|
||||
$hashLookup = &drupal_static(__FUNCTION__, null);
|
||||
|
||||
if (null === $hashLookup) {
|
||||
try {
|
||||
$className = Redis_Client::getClass(Redis_Client::REDIS_IMPL_PATH);
|
||||
$hashLookup = new $className(Redis_Client::getClient(), 'path', Redis_Client::getDefaultPrefix('path'));
|
||||
} catch (Exception $e) {
|
||||
$hashLookup = new Redis_Path_NullHashLookup();
|
||||
}
|
||||
}
|
||||
|
||||
return $hashLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the $_GET['q'] variable to the proper normal path.
|
||||
*/
|
||||
function drupal_path_initialize() {
|
||||
// Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
|
||||
// path caching with hook_url_inbound_alter().
|
||||
if (empty($_GET['q'])) {
|
||||
$_GET['q'] = variable_get('site_frontpage', 'node');
|
||||
}
|
||||
$_GET['q'] = drupal_get_normal_path($_GET['q']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an alias, return its Drupal system URL if one exists. Given a Drupal
|
||||
* system URL return one of its aliases if such a one exists. Otherwise,
|
||||
* return FALSE.
|
||||
*
|
||||
* @param $action
|
||||
* One of the following values:
|
||||
* - wipe: delete the alias cache.
|
||||
* - alias: return an alias for a given Drupal system path (if one exists).
|
||||
* - source: return the Drupal system URL for a path alias (if one exists).
|
||||
* @param $path
|
||||
* The path to investigate for corresponding aliases or system URLs.
|
||||
* @param $path_language
|
||||
* Optional language code to search the path with. Defaults to the page language.
|
||||
* If there's no path defined for that language it will search paths without
|
||||
* language.
|
||||
*
|
||||
* @return
|
||||
* Either a Drupal system path, an aliased path, or FALSE if no path was
|
||||
* found.
|
||||
*/
|
||||
function drupal_lookup_path($action, $path = '', $path_language = NULL) {
|
||||
global $language_url;
|
||||
|
||||
static $cache, $denyAdmin;
|
||||
|
||||
if (null === $cache) {
|
||||
$cache = array('whitelist' => variable_get('path_alias_whitelist'));
|
||||
if (null === $cache['whitelist']) {
|
||||
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
|
||||
}
|
||||
$denyAdmin = (bool)variable_get('path_alias_admin_blacklist', true);
|
||||
}
|
||||
|
||||
// If no language is explicitly specified we default to the current URL
|
||||
// language. If we used a language different from the one conveyed by the
|
||||
// requested URL, we might end up being unable to check if there is a path
|
||||
// alias matching the URL path.
|
||||
if (!$path_language = $path_language ? $path_language : $language_url->language) {
|
||||
$path_language = LANGUAGE_NONE;
|
||||
}
|
||||
|
||||
if (!empty($path) && isset($cache[$path_language][$action][$path])) {
|
||||
return $cache[$path_language][$action][$path];
|
||||
}
|
||||
|
||||
if (!empty($path)) {
|
||||
$path = strtolower(trim($path));
|
||||
}
|
||||
|
||||
$ret = null;
|
||||
$hashLookup = redis_path_backend_get();
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'wipe':
|
||||
$cache = array();
|
||||
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
|
||||
break;
|
||||
|
||||
case 'alias':
|
||||
if (empty($path)) {
|
||||
return false;
|
||||
}
|
||||
// Check the path whitelist, if the top_level part before the first /
|
||||
// is not in the list, then there is no need to do anything further,
|
||||
// it is not in the database.
|
||||
if (!isset($cache['whitelist'][strtok($path, '/')])) {
|
||||
return false;
|
||||
}
|
||||
// Deny admin paths.
|
||||
if ($denyAdmin && path_is_admin($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = $hashLookup->lookupAlias($path, $path_language);
|
||||
if (null === $ret) {
|
||||
// Original Drupal algorithm.
|
||||
// This will also update the $path_language variable so Redis will store
|
||||
// the right language (keeps track of LANGUAGE_NONE or specific language
|
||||
// so that default fallback behavior is the same that core).
|
||||
if ($path_language == LANGUAGE_NONE) {
|
||||
list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language = :language ORDER BY pid DESC", 0, 1, array(
|
||||
':source' => $path,
|
||||
':language' => $path_language,
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
} else if ($path_language > LANGUAGE_NONE) {
|
||||
list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language IN (:language) ORDER BY language DESC, pid DESC", 0, 1, array(
|
||||
':source' => $path,
|
||||
':language' => array($path_language, LANGUAGE_NONE),
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
} else {
|
||||
list ($ret, $path_language) = db_query_range("SELECT alias, language FROM {url_alias} WHERE source = :source AND language IN (:language) ORDER BY language ASC, pid DESC", 0, 1, array(
|
||||
':source' => $path,
|
||||
':language' => array($path_language, LANGUAGE_NONE),
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
// Getting here with a value means we need to cache it
|
||||
if (empty($ret)) {
|
||||
$ret = false;
|
||||
}
|
||||
$hashLookup->saveAlias($path, $ret, $path_language);
|
||||
}
|
||||
$cache[$path_language]['alias'][$path] = $ret;
|
||||
$cache[$path_language]['source'][$ret] = $path;
|
||||
break;
|
||||
|
||||
case 'source':
|
||||
if (empty($path)) {
|
||||
return false;
|
||||
}
|
||||
// Even thought given entry is an alias, if it conflicts with an
|
||||
// existing admin path just deny any lookup.
|
||||
if ($denyAdmin && path_is_admin($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ret = $hashLookup->lookupSource($path, $path_language);
|
||||
if (null === $ret) {
|
||||
// Original Drupal algorithm.
|
||||
// This will also update the $path_language variable so Redis will store
|
||||
// the right language (keeps track of LANGUAGE_NONE or specific language
|
||||
// so that default fallback behavior is the same that core).
|
||||
if ($path_language == LANGUAGE_NONE) {
|
||||
list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language = :language ORDER BY pid DESC", 0, 1, array(
|
||||
':alias' => $path,
|
||||
':language' => LANGUAGE_NONE,
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
} else if ($path_language > LANGUAGE_NONE) {
|
||||
list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language IN (:language) ORDER BY language DESC, pid DESC", 0, 1, array(
|
||||
':alias' => $path,
|
||||
':language' => array($path_language, LANGUAGE_NONE),
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
} else {
|
||||
list ($ret, $path_language) = db_query_range("SELECT source, language FROM {url_alias} WHERE alias = :alias AND language IN (:language) ORDER BY language ASC, pid DESC", 0, 1, array(
|
||||
':alias' => $path,
|
||||
':language' => array($path_language, LANGUAGE_NONE),
|
||||
))->fetch(PDO::FETCH_NUM);
|
||||
}
|
||||
// Getting here with a value means we need to cache it
|
||||
if (empty($ret)) {
|
||||
$ret = false;
|
||||
} else {
|
||||
$ret = strtolower(trim($ret));
|
||||
}
|
||||
$hashLookup->saveAlias($ret, $path, $path_language);
|
||||
}
|
||||
$cache[$path_language]['alias'][$ret] = $path;
|
||||
$cache[$path_language]['source'][$path] = $ret;
|
||||
break;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache system paths for a page.
|
||||
*
|
||||
* Cache an array of the system paths available on each page. We assume
|
||||
* that aliases will be needed for the majority of these paths during
|
||||
* subsequent requests, and load them in a single query during
|
||||
* drupal_lookup_path().
|
||||
*/
|
||||
function drupal_cache_system_paths() {
|
||||
// Check if the system paths for this page were loaded from cache in this
|
||||
// request to avoid writing to cache on every request.
|
||||
$cache = &drupal_static('drupal_lookup_path', array());
|
||||
if (empty($cache['system_paths']) && !empty($cache['map'])) {
|
||||
// Generate a cache ID (cid) specifically for this page.
|
||||
$cid = current_path();
|
||||
// The static $map array used by drupal_lookup_path() includes all
|
||||
// system paths for the page request.
|
||||
if ($paths = current($cache['map'])) {
|
||||
$data = array_keys($paths);
|
||||
$expire = REQUEST_TIME + (60 * 60 * 24);
|
||||
cache_set($cid, $data, 'cache_path', $expire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an internal Drupal path, return the alias set by the administrator.
|
||||
*
|
||||
* If no path is provided, the function will return the alias of the current
|
||||
* page.
|
||||
*
|
||||
* @param $path
|
||||
* An internal Drupal path.
|
||||
* @param $path_language
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return
|
||||
* An aliased path if one was found, or the original path if no alias was
|
||||
* found.
|
||||
*/
|
||||
function drupal_get_path_alias($path = NULL, $path_language = NULL) {
|
||||
// If no path is specified, use the current page's path.
|
||||
if ($path == NULL) {
|
||||
$path = $_GET['q'];
|
||||
}
|
||||
$result = $path;
|
||||
if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
|
||||
$result = $alias;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path alias, return the internal path it represents.
|
||||
*
|
||||
* @param $path
|
||||
* A Drupal path alias.
|
||||
* @param $path_language
|
||||
* An optional language code to look up the path in.
|
||||
*
|
||||
* @return
|
||||
* The internal path represented by the alias, or the original alias if no
|
||||
* internal path was found.
|
||||
*/
|
||||
function drupal_get_normal_path($path, $path_language = NULL) {
|
||||
$original_path = $path;
|
||||
|
||||
// Lookup the path alias first.
|
||||
if ($source = drupal_lookup_path('source', $path, $path_language)) {
|
||||
$path = $source;
|
||||
}
|
||||
|
||||
// Allow other modules to alter the inbound URL. We cannot use drupal_alter()
|
||||
// here because we need to run hook_url_inbound_alter() in the reverse order
|
||||
// of hook_url_outbound_alter().
|
||||
foreach (array_reverse(module_implements('url_inbound_alter')) as $module) {
|
||||
$function = $module . '_url_inbound_alter';
|
||||
$function($path, $original_path, $path_language);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is the front page.
|
||||
*
|
||||
* @return
|
||||
* Boolean value: TRUE if the current page is the front page; FALSE if otherwise.
|
||||
*/
|
||||
function drupal_is_front_page() {
|
||||
// Use the advanced drupal_static() pattern, since this is called very often.
|
||||
static $drupal_static_fast;
|
||||
if (!isset($drupal_static_fast)) {
|
||||
$drupal_static_fast['is_front_page'] = &drupal_static(__FUNCTION__);
|
||||
}
|
||||
$is_front_page = &$drupal_static_fast['is_front_page'];
|
||||
|
||||
if (!isset($is_front_page)) {
|
||||
// As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path,
|
||||
// we can check it against the 'site_frontpage' variable.
|
||||
$is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'node'));
|
||||
}
|
||||
|
||||
return $is_front_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path matches any pattern in a set of patterns.
|
||||
*
|
||||
* @param $path
|
||||
* The path to match.
|
||||
* @param $patterns
|
||||
* String containing a set of patterns separated by \n, \r or \r\n.
|
||||
*
|
||||
* @return
|
||||
* Boolean value: TRUE if the path matches a pattern, FALSE otherwise.
|
||||
*/
|
||||
function drupal_match_path($path, $patterns) {
|
||||
$regexps = &drupal_static(__FUNCTION__);
|
||||
|
||||
if (!isset($regexps[$patterns])) {
|
||||
// Convert path settings to a regular expression.
|
||||
// Therefore replace newlines with a logical or, /* with asterisks and the <front> with the frontpage.
|
||||
$to_replace = array(
|
||||
'/(\r\n?|\n)/', // newlines
|
||||
'/\\\\\*/', // asterisks
|
||||
'/(^|\|)\\\\<front\\\\>($|\|)/' // <front>
|
||||
);
|
||||
$replacements = array(
|
||||
'|',
|
||||
'.*',
|
||||
'\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2'
|
||||
);
|
||||
$patterns_quoted = preg_quote($patterns, '/');
|
||||
$regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/';
|
||||
}
|
||||
return (bool)preg_match($regexps[$patterns], $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current URL path of the page being viewed.
|
||||
*
|
||||
* Examples:
|
||||
* - http://example.com/node/306 returns "node/306".
|
||||
* - http://example.com/drupalfolder/node/306 returns "node/306" while
|
||||
* base_path() returns "/drupalfolder/".
|
||||
* - http://example.com/path/alias (which is a path alias for node/306) returns
|
||||
* "node/306" as opposed to the path alias.
|
||||
*
|
||||
* This function is not available in hook_boot() so use $_GET['q'] instead.
|
||||
* However, be careful when doing that because in the case of Example #3
|
||||
* $_GET['q'] will contain "path/alias". If "node/306" is needed, calling
|
||||
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available.
|
||||
*
|
||||
* @return
|
||||
* The current Drupal URL path.
|
||||
*
|
||||
* @see request_path()
|
||||
*/
|
||||
function current_path() {
|
||||
return $_GET['q'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the path alias white list.
|
||||
*
|
||||
* @param $source
|
||||
* An optional system path for which an alias is being inserted.
|
||||
*
|
||||
* @return
|
||||
* An array containing a white list of path aliases.
|
||||
*/
|
||||
function drupal_path_alias_whitelist_rebuild($source = NULL) {
|
||||
// When paths are inserted, only rebuild the whitelist if the system path
|
||||
// has a top level component which is not already in the whitelist.
|
||||
if (!empty($source)) {
|
||||
$whitelist = variable_get('path_alias_whitelist', NULL);
|
||||
if (isset($whitelist[strtok($source, '/')])) {
|
||||
return $whitelist;
|
||||
}
|
||||
}
|
||||
// For each alias in the database, get the top level component of the system
|
||||
// path it corresponds to. This is the portion of the path before the first
|
||||
// '/', if present, otherwise the whole path itself.
|
||||
$whitelist = array();
|
||||
$result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}");
|
||||
foreach ($result as $row) {
|
||||
$whitelist[$row->path] = TRUE;
|
||||
}
|
||||
variable_set('path_alias_whitelist', $whitelist);
|
||||
return $whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a specific URL alias from the database.
|
||||
*
|
||||
* @param $conditions
|
||||
* A string representing the source, a number representing the pid, or an
|
||||
* array of query conditions.
|
||||
*
|
||||
* @return
|
||||
* FALSE if no alias was found or an associative array containing the
|
||||
* following keys:
|
||||
* - source: The internal system path.
|
||||
* - alias: The URL alias.
|
||||
* - pid: Unique path alias identifier.
|
||||
* - language: The language of the alias.
|
||||
*/
|
||||
function path_load($conditions) {
|
||||
if (is_numeric($conditions)) {
|
||||
$conditions = array('pid' => $conditions);
|
||||
}
|
||||
elseif (is_string($conditions)) {
|
||||
$conditions = array('source' => $conditions);
|
||||
}
|
||||
elseif (!is_array($conditions)) {
|
||||
return FALSE;
|
||||
}
|
||||
$select = db_select('url_alias');
|
||||
foreach ($conditions as $field => $value) {
|
||||
$select->condition($field, $value);
|
||||
}
|
||||
return $select
|
||||
->fields('url_alias')
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a path alias to the database.
|
||||
*
|
||||
* @param $path
|
||||
* An associative array containing the following keys:
|
||||
* - source: The internal system path.
|
||||
* - alias: The URL alias.
|
||||
* - pid: (optional) Unique path alias identifier.
|
||||
* - language: (optional) The language of the alias.
|
||||
*/
|
||||
function path_save(&$path) {
|
||||
$path += array('language' => LANGUAGE_NONE);
|
||||
|
||||
// Load the stored alias, if any.
|
||||
if (!empty($path['pid']) && !isset($path['original'])) {
|
||||
$path['original'] = path_load($path['pid']);
|
||||
}
|
||||
|
||||
if (empty($path['pid'])) {
|
||||
drupal_write_record('url_alias', $path);
|
||||
module_invoke_all('path_insert', $path);
|
||||
}
|
||||
else {
|
||||
drupal_write_record('url_alias', $path, array('pid'));
|
||||
module_invoke_all('path_update', $path);
|
||||
}
|
||||
if (!empty($path['original'])) {
|
||||
redis_path_backend_get()->deleteAlias($path['original']['source'], $path['original']['alias'], $path['original']['language']);
|
||||
}
|
||||
redis_path_backend_get()->saveAlias($path['source'], $path['alias'], $path['language']);
|
||||
|
||||
// Clear internal properties.
|
||||
unset($path['original']);
|
||||
|
||||
// Clear the static alias cache.
|
||||
drupal_clear_path_cache($path['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a URL alias.
|
||||
*
|
||||
* @param $criteria
|
||||
* A number representing the pid or an array of criteria.
|
||||
*/
|
||||
function path_delete($criteria) {
|
||||
if (!is_array($criteria)) {
|
||||
$criteria = array('pid' => $criteria);
|
||||
}
|
||||
$path = path_load($criteria);
|
||||
$query = db_delete('url_alias');
|
||||
foreach ($criteria as $field => $value) {
|
||||
$query->condition($field, $value);
|
||||
}
|
||||
$query->execute();
|
||||
module_invoke_all('path_delete', $path);
|
||||
redis_path_backend_get()->deleteAlias($path['source'], $path['alias'], $path['language']);
|
||||
drupal_clear_path_cache($path['source']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a path is in the administrative section of the site.
|
||||
*
|
||||
* By default, paths are considered to be non-administrative. If a path does
|
||||
* not match any of the patterns in path_get_admin_paths(), or if it matches
|
||||
* both administrative and non-administrative patterns, it is considered
|
||||
* non-administrative.
|
||||
*
|
||||
* @param $path
|
||||
* A Drupal path.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the path is administrative, FALSE otherwise.
|
||||
*
|
||||
* @see path_get_admin_paths()
|
||||
* @see hook_admin_paths()
|
||||
* @see hook_admin_paths_alter()
|
||||
*/
|
||||
function path_is_admin($path) {
|
||||
$path_map = &drupal_static(__FUNCTION__);
|
||||
if (!isset($path_map['admin'][$path])) {
|
||||
$patterns = path_get_admin_paths();
|
||||
$path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']);
|
||||
$path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']);
|
||||
}
|
||||
return $path_map['admin'][$path] && !$path_map['non_admin'][$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of administrative and non-administrative paths.
|
||||
*
|
||||
* @return array
|
||||
* An associative array containing the following keys:
|
||||
* 'admin': An array of administrative paths and regular expressions
|
||||
* in a format suitable for drupal_match_path().
|
||||
* 'non_admin': An array of non-administrative paths and regular expressions.
|
||||
*
|
||||
* @see hook_admin_paths()
|
||||
* @see hook_admin_paths_alter()
|
||||
*/
|
||||
function path_get_admin_paths() {
|
||||
$patterns = &drupal_static(__FUNCTION__);
|
||||
if (!isset($patterns)) {
|
||||
$paths = module_invoke_all('admin_paths');
|
||||
drupal_alter('admin_paths', $paths);
|
||||
// Combine all admin paths into one array, and likewise for non-admin paths,
|
||||
// for easier handling.
|
||||
$patterns = array();
|
||||
$patterns['admin'] = array();
|
||||
$patterns['non_admin'] = array();
|
||||
foreach ($paths as $path => $enabled) {
|
||||
if ($enabled) {
|
||||
$patterns['admin'][] = $path;
|
||||
}
|
||||
else {
|
||||
$patterns['non_admin'][] = $path;
|
||||
}
|
||||
}
|
||||
$patterns['admin'] = implode("\n", $patterns['admin']);
|
||||
$patterns['non_admin'] = implode("\n", $patterns['non_admin']);
|
||||
}
|
||||
return $patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a path exists and the current user has access to it.
|
||||
*
|
||||
* @param $path
|
||||
* The path to check.
|
||||
* @param $dynamic_allowed
|
||||
* Whether paths with menu wildcards (like user/%) should be allowed.
|
||||
*
|
||||
* @return
|
||||
* TRUE if it is a valid path AND the current user has access permission,
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
function drupal_valid_path($path, $dynamic_allowed = FALSE) {
|
||||
global $menu_admin;
|
||||
// We indicate that a menu administrator is running the menu access check.
|
||||
$menu_admin = TRUE;
|
||||
if ($path == '<front>' || url_is_external($path)) {
|
||||
$item = array('access' => TRUE);
|
||||
}
|
||||
elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) {
|
||||
// Path is dynamic (ie 'user/%'), so check directly against menu_router table.
|
||||
if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) {
|
||||
$item['link_path'] = $item['path'];
|
||||
$item['link_title'] = $item['title'];
|
||||
$item['external'] = FALSE;
|
||||
$item['options'] = '';
|
||||
_menu_link_translate($item);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$item = menu_get_item($path);
|
||||
}
|
||||
$menu_admin = FALSE;
|
||||
return $item && $item['access'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the path cache.
|
||||
*
|
||||
* @param $source
|
||||
* An optional system path for which an alias is being changed.
|
||||
*/
|
||||
function drupal_clear_path_cache($source = NULL) {
|
||||
// Clear the drupal_lookup_path() static cache.
|
||||
drupal_static_reset('drupal_lookup_path');
|
||||
drupal_path_alias_whitelist_rebuild($source);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user