Meik Baum
10 months ago
3853 changed files with 574828 additions and 123 deletions
@ -1,2 +0,0 @@ |
|||
* |
|||
!.gitignore |
@ -1,5 +0,0 @@ |
|||
config.php |
|||
db-config.php |
|||
dev-config.json |
|||
settings.php |
|||
updated.txt |
@ -0,0 +1,851 @@ |
|||
<?php |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/////////////////////// Copy this file to settings.php and edit the options. /////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
//////////////////////////// Scroll down to the bottom for a change log. /////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
|
|||
use nzedb\utility\Misc; |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
////////////////////////////////////////// MISC //////////////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* When we update settings.example.php, we will raise this version, you will get a message saying |
|||
* your settings.php is out of date, you will need to update it and change the version number. |
|||
* |
|||
* @note Developers: When updating settings.example.php, up this version and |
|||
* $current_settings_file_version in nzedb\config\Configure.php |
|||
* @version 4 |
|||
*/ |
|||
\define('nZEDb_SETTINGS_FILE_VERSION', 5); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
////////////////////////////////////// Web Settings //////////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* How many releases to show per page in list view. |
|||
* |
|||
* @default '50' |
|||
*/ |
|||
\define('ITEMS_PER_PAGE', 50); |
|||
|
|||
/** |
|||
* How many releases to show per page in cover view. |
|||
* |
|||
* @default '20' |
|||
*/ |
|||
\define('ITEMS_PER_COVER_PAGE', 20); |
|||
|
|||
/** |
|||
* How many releases maximum to display in total on browse/search/etc. |
|||
* If you have ITEMS_PER_PAGE set to 50, and nZEDb_MAX_PAGER_RESULTS set to 125000, you would get a maximum of |
|||
* 2,500 pages of results in searches/browse. |
|||
* |
|||
* @note This setting can speed up browsing releases tremendously if you have millions of releases and you keep it |
|||
* a relatively low value. |
|||
* @default '125000' |
|||
*/ |
|||
\define('nZEDb_MAX_PAGER_RESULTS', 125000); |
|||
|
|||
/** |
|||
* If the PRE API page (preinfo) is open to the public or only accessible by registered / api users. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_PREINFO_OPEN', false); |
|||
|
|||
/** |
|||
* Whether to check if a person is trying to send too many requests in a given amount of time, |
|||
* lock out the person of the site for a amount of time. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_FLOOD_CHECK', false); |
|||
|
|||
/** |
|||
* How many seconds should the person be locked out of the site. |
|||
* |
|||
* @default 5 |
|||
*/ |
|||
\define('nZEDb_FLOOD_WAIT_TIME', 5); |
|||
|
|||
/** |
|||
* How many requests in a second can a person send to the site max before being locked out for |
|||
* nZEDb_FLOOD_WAIT_TIME seconds. |
|||
* |
|||
* @default 5 |
|||
*/ |
|||
\define('nZEDb_FLOOD_MAX_REQUESTS_PER_SECOND', 5); |
|||
|
|||
/** |
|||
* The higher this number, the more secure the password algorithm for the website will be, at the |
|||
* cost of server resources. To find a good number for your server, run the |
|||
* .../misc/testing/Various/find_password_hash_cost.php script. |
|||
* |
|||
* @note It is not recommended to set this under 11. |
|||
* @default 11 |
|||
*/ |
|||
\define('nZEDb_PASSWORD_HASH_COST', 11); |
|||
|
|||
/** |
|||
* The type of search system to use on the site. |
|||
* 0 = The default system, which uses fulltext indexing (very fast but search results can be unexpected). |
|||
* 1 = The old search system from newznab classic (much slower but produces better search results). |
|||
* 2 = Search using sphinx real time index, see misc/sphinxsearch/README.md for installation details. |
|||
* |
|||
* @default 0 |
|||
*/ |
|||
\define('nZEDb_RELEASE_SEARCH_TYPE', 0); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/////////////////////////////////////// Sphinx Settings //////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
|
|||
/** |
|||
* This is the hostname to use when connecting to the SphinxQL server (mysql41 protocol), |
|||
* |
|||
* @note Using localhost / 127.0.0.1 has caused me issues and only 0 worked on my local server. |
|||
* @note See misc/sphinxsearch/README.md for installation details. |
|||
* @default '0' |
|||
*/ |
|||
\define('nZEDb_SPHINXQL_HOST_NAME', '0'); |
|||
|
|||
/** |
|||
* This is the port to the SphinxQL server (mysql41 protocol). |
|||
* |
|||
* @default 9306 |
|||
*/ |
|||
\define('nZEDb_SPHINXQL_PORT', 9306); |
|||
|
|||
/** |
|||
* This is the (optional) location to the SphinxQL server socket file, if you set the "listen" |
|||
* setting to a sock file. |
|||
* |
|||
* @default '' |
|||
*/ |
|||
\define('nZEDb_SPHINXQL_SOCK_FILE', ''); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
///////////////////////////////////////// CLI Settings ///////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Display text to console(terminal) output. |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_ECHOCLI', true); |
|||
|
|||
/** |
|||
* Rename releases using PAR2 files (if they match on PRE titles)? |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_RENAME_PAR2', true); |
|||
|
|||
/** |
|||
* Rename music releases using media info from the MP3/FLAC/etc tags (names are created using info |
|||
* found in the tags)? |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_RENAME_MUSIC_MEDIAINFO', true); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
//////////////////////////////////////// Cache Settings //////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Type of cache server(s) to use: |
|||
* 0 - disabled ; No cache server(s) will be used. |
|||
* 1 - memcached ; Memcached server(s) will be used for caching. |
|||
* 2 - redis ; Redis server(s) will be used for caching. |
|||
* 3 - apc/apcu ; APC or APCu will be used for caching. |
|||
* |
|||
* @see https://github.com/nZEDb/nZEDb_Misc/blob/master/Guides/Various/Cache/Guide.md |
|||
* @note Memcahed: The Memcached PHP extension must be installed. |
|||
* @note Redis: We use the Redis PHP extension by nicolasff. https://github.com/nicolasff/phpredis |
|||
* @note APC: The APC or APCu PHP extension must be installed. |
|||
* @note APC: Ignore these settings: nZEDb_CACHE_HOSTS / nZEDb_CACHE_SOCKET_FILE / nZEDb_CACHE_TIMEOUT |
|||
* @default 0 |
|||
* @version 3 |
|||
*/ |
|||
\define('nZEDb_CACHE_TYPE', 0); |
|||
|
|||
/** |
|||
* List of redis or memcached servers to connect to. Separate them by comma. |
|||
* Host: (string) Address for the cache server. '127.0.0.1' for a local server. |
|||
* Port: (integer) Default for memcached is 11211, Default for redis is 6379 |
|||
* Weight: (integer) On redis, this is unused, set it to 0. |
|||
* On memcached if you have 1 memcached server, you set this to 100. |
|||
* If you have more than 1 memcached server, see the memcached documentation for more info: |
|||
* http://php.net/manual/en/memcached.addserver.php |
|||
*/ |
|||
\define('nZEDb_CACHE_HOSTS', |
|||
serialize( |
|||
[ |
|||
'Server1' => [ |
|||
'host' => '127.0.0.1', |
|||
'port' => 11211, |
|||
'weight' => 0 |
|||
], |
|||
] |
|||
)); |
|||
|
|||
/** |
|||
* Optional path to unix socket file, leave '' if to not use. |
|||
* If using a unix socket file, the server list is overridden. |
|||
* This should be faster than using the host/port if your cache server is local. |
|||
* |
|||
* @example '/var/run/redis/redis.sock' |
|||
* @note By default, redis and memcached do not have a socket file, you must configure them. |
|||
* @note Read and write access is required to the socket file. |
|||
* @default '' |
|||
*/ |
|||
\define('nZEDb_CACHE_SOCKET_FILE', ''); |
|||
|
|||
/** |
|||
* Timeout for connecting to cache server(s). |
|||
* |
|||
* @default 10 |
|||
*/ |
|||
\define('nZEDb_CACHE_TIMEOUT', 10); |
|||
|
|||
/** |
|||
* Memcached allows to compress the data, saving RAM at the expense of CPU time. |
|||
* |
|||
* @note Does nothing on redis. |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_CACHE_COMPRESSION', false); |
|||
|
|||
/** |
|||
* Serialization is a way of converting data in PHP into strings of text which can be stored on the |
|||
* cache server. |
|||
* 0 - Use the PHP serializer. Recommended for most people. |
|||
* 1 - [Requires igbinary] Use igbinary serializer which is faster and uses less memory, works |
|||
* on Memcached / Redis / APC, read the notes below. |
|||
* 2 - [Redis Only] Use no serializer. |
|||
* |
|||
* @note igbinary must be compiled and enabled in php.ini |
|||
* @note APC/APCu: This setting is ignored, set this in php.ini with apc.serializer |
|||
* @note Memcached/Redis must be compiled with igbinary support as well to use igbinary. |
|||
* @note Read the igbinary page how to compile / enable. |
|||
* @see https://github.com/phadej/igbinary |
|||
* @default 0 |
|||
* @version 3 |
|||
*/ |
|||
\define('nZEDb_CACHE_SERIALIZER', 0); |
|||
|
|||
/** |
|||
* Amount of time in seconds to expire data from the cache server. |
|||
* The developers of nZEDb decide what should be set as short/medium/long, depending on the type of data. |
|||
* |
|||
* @defaults 300/600/900 |
|||
*/ |
|||
\define('nZEDb_CACHE_EXPIRY_SHORT', 300); |
|||
\define('nZEDb_CACHE_EXPIRY_MEDIUM', 600); |
|||
\define('nZEDb_CACHE_EXPIRY_LONG', 900); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/////////////////////////////////////// Log Settings /////////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Display debug messages on console or web page. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_DEBUG', false); |
|||
|
|||
/** |
|||
* Log debug messages to nzedb/resources/debug.log |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGGING', true); |
|||
|
|||
/** |
|||
* var_dump missing autoloader files. |
|||
* |
|||
* @note Dev setting. |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGAUTOLOADER', false); |
|||
|
|||
/** |
|||
* How many log files to keep in the log folder. |
|||
* |
|||
* @default 20 |
|||
*/ |
|||
\define('nZEDb_LOGGING_MAX_LOGS', 20); |
|||
|
|||
/** |
|||
* How large can the log files be in MegaBytes before we create a new one? The old files are |
|||
* compressed. |
|||
* |
|||
* @default 30 |
|||
*/ |
|||
\define('nZEDb_LOGGING_MAX_SIZE', 30); |
|||
|
|||
/** |
|||
* The folder to put the log files in. Put quotes, example : '/var/log/nZEDb/' |
|||
* The default is in the nZEDb root folder /resources/logs/ |
|||
* |
|||
* @example '/var/log/nZEDb/' |
|||
* @default nZEDb_LOGS |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_FOLDER', nZEDb_LOGS); |
|||
|
|||
/** |
|||
* The name of the log file. |
|||
* Must be alphanumeric (a-z 0-9) and contain no file extensions. |
|||
* |
|||
* @default 'nzedb' |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_NAME', 'nzedb'); |
|||
|
|||
/** |
|||
* Display memory usage in log file and debug message output? |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_MEMORY_USAGE', true); |
|||
|
|||
/** |
|||
* Display CPU load in log file and debug message output? |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_CPU_LOAD', true); |
|||
|
|||
/** |
|||
* Display running time in log file and debug message output? |
|||
* |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_RUNNING_TIME', true); |
|||
|
|||
/** |
|||
* Display resource usage in log file and debug message output? |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGGING_LOG_RESOURCE_USAGE', true); |
|||
|
|||
/********************************************************************************* |
|||
* The following options require either nZEDb_DEBUG OR nZEDb_LOGGING to be true: * |
|||
*********************************************************************************/ |
|||
|
|||
/** |
|||
* Log and/or echo debug Info messages. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGINFO', true); |
|||
|
|||
/** |
|||
* Log and/or echo debug Notice messages. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGNOTICE', true); |
|||
|
|||
/** |
|||
* Log and/or echo debug Warning messages. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGWARNING', true); |
|||
|
|||
/** |
|||
* Log and/or echo debug Error messages. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGERROR', true); |
|||
|
|||
/** |
|||
* Log and/or echo debug Fatal messages. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGFATAL', true); |
|||
|
|||
/** |
|||
* Log and/or echo debug failed SQL queries. |
|||
* |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_LOGQUERIES', true); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
//////////////////////////////////////// SQL Settings ////////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Strip white space (space, carriage return, new line, tab, etc) from queries before sending to |
|||
* MySQL. This is useful if you use the MySQL slow query log. |
|||
* |
|||
* @note This slows down query processing, leave it false unless you turn on the MySQL slow query |
|||
* log. |
|||
* @default false |
|||
*/ |
|||
\define('nZEDb_QUERY_STRIP_WHITESPACE', false); |
|||
|
|||
/** |
|||
* Use transactions when doing certain SQL jobs. |
|||
* This has advantages and disadvantages. |
|||
* If there's a problem during a transaction, MySQL can revert the row inserts which is beneficial. |
|||
* Transactions can cause deadlocks however if you are trying to insert into the same table from another process. |
|||
* |
|||
* @note If all your tables are MyISAM you can set this to false, as MyISAM does not support transactions. |
|||
* @default true |
|||
*/ |
|||
\define('nZEDb_USE_SQL_TRANSACTIONS', true); |
|||
|
|||
/** |
|||
* Allows the use of LOW_PRIORITY in certain DELETE queries. |
|||
* This prevents table locks by deleting only when no SELECT queries are active on the table. |
|||
* This works on MyISAM/ARIA, not INNODB. |
|||
* |
|||
* @note Does not cause any errors or warnings if enabled on INNODB. |
|||
* @link https://dev.mysql.com/doc/refman/5.7/en/delete.html |
|||
* @default false |
|||
* @version 1 |
|||
*/ |
|||
\define('nZEDb_SQL_DELETE_LOW_PRIORITY', false); |
|||
|
|||
/** |
|||
* Allows the use QUICK in certain DELETE queries. |
|||
* This makes DELETE queries faster on MyISAM/ARIA tables by not merging index leaves. |
|||
* Only supported on MyISAM/ARIA |
|||
* |
|||
* @note Does not cause any errors or warnings if enabled on INNODB. |
|||
* @link https://dev.mysql.com/doc/refman/5.7/en/delete.html |
|||
* @default false |
|||
* @version 1 |
|||
*/ |
|||
\define('nZEDb_SQL_DELETE_QUICK', false); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
//////////////////////////////////// PHPMailer Settings //////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Simple constant to let us know this file is included and we should use PHPMailer library. |
|||
* Uncomment the line below after setting the other constants. |
|||
*/ |
|||
\define('PHPMAILER_ENABLED', false); |
|||
|
|||
/** |
|||
* Global "From" Address. |
|||
* This address will be set as the From: address on every email sent by nZEDb. |
|||
* |
|||
* @example 'noreply@example.com' |
|||
* @note Depending on server configurations, it may not respect this value. |
|||
* @default '' (uses the contact email configured in 'Edit Site' settings) |
|||
*/ |
|||
\define('PHPMAILER_FROM_EMAIL', ''); |
|||
|
|||
/** |
|||
* Global "From" Name. |
|||
* Along with the email above, this will display as the name. |
|||
* |
|||
* @example 'KingCat' |
|||
* @note Depending on server configurations, it may not respect this value. |
|||
* @default '' (uses the site title configured in 'Edit Site' settings) |
|||
*/ |
|||
\define('PHPMAILER_FROM_NAME', ''); |
|||
|
|||
/** |
|||
* Global "Reply-to" Address. |
|||
* This address will be set as the Reply-to: address on every email sent by nZEDb. |
|||
* |
|||
* @example 'support@example.com' |
|||
* @note It's a good idea to set this to your support email account (if possible) |
|||
* @default '' (uses the contact email configured in 'Edit Site' settings) |
|||
*/ |
|||
\define('PHPMAILER_REPLYTO', ''); |
|||
|
|||
/** |
|||
* Always BCC. |
|||
* This email address will be blind carbon copied on every email sent from this site. |
|||
* |
|||
* @note This has very specific uses, don't enable unless you're sure you want to get the deluge. |
|||
* @default '' |
|||
*/ |
|||
\define('PHPMAILER_BCC', ''); |
|||
|
|||
/** |
|||
* Should we use a SMTP server to send mail? |
|||
* If false, it will use your default settings from php.ini. |
|||
* |
|||
* @note If set to true, be sure to set the server settings below. |
|||
* @default false |
|||
*/ |
|||
\define('PHPMAILER_USE_SMTP', false); |
|||
|
|||
/********************************************************************************* |
|||
* The following options require PHPMAILER_USE_SMTP to be true: * |
|||
*********************************************************************************/ |
|||
|
|||
/** |
|||
* This is the hostname to use if connecting to a SMTP server. |
|||
* |
|||
* @note You can specify main and backup hosts, delimit with a semicolon. (i.e. 'main.host.com;backup.host.com') |
|||
* @default '' |
|||
*/ |
|||
\define('PHPMAILER_SMTP_HOST', ''); |
|||
|
|||
/** |
|||
* TLS & SSL Support for your SMTP server. |
|||
* |
|||
* @note Possible values: false, 'tls', 'ssl' |
|||
* @default 'tls' |
|||
*/ |
|||
\define('PHPMAILER_SMTP_SECURE', 'tls'); |
|||
|
|||
/** |
|||
* SMTP Port |
|||
* |
|||
* @note Usually this is 25, 465, or 587 |
|||
* @default 587 |
|||
*/ |
|||
\define('PHPMAILER_SMTP_PORT', 587); |
|||
|
|||
/** |
|||
* Does your SMTP host require authentication? |
|||
* |
|||
* @note Be sure to set credentials below if changing to true. |
|||
* @default false |
|||
*/ |
|||
\define('PHPMAILER_SMTP_AUTH', false); |
|||
|
|||
/********************************************************************************* |
|||
* The following options require both PHPMAILER_USE_SMTP & PHPMAILER_SMTP_AUTH to be true: * |
|||
*********************************************************************************/ |
|||
|
|||
/** |
|||
* SMTP username for authentication. |
|||
* |
|||
* @default '' |
|||
*/ |
|||
\define('PHPMAILER_SMTP_USER', ''); |
|||
|
|||
/** |
|||
* SMTP password for authentication. |
|||
* |
|||
* @default '' |
|||
*/ |
|||
\define('PHPMAILER_SMTP_PASSWORD', ''); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
////////////////////////////////// PHP CLI Settings //////////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
if (Misc::isCLI()) { |
|||
|
|||
/** |
|||
* Your server's local timezone. |
|||
* |
|||
* @note Uncomment to enable. |
|||
* @see https://secure.php.net/manual/en/timezones.php |
|||
* @version 4 |
|||
*/ |
|||
//ini_set('date.timezone', 'America/New_York'); |
|||
|
|||
/** |
|||
* Maximum amount of memory a PHP script can consume before being terminated. |
|||
* |
|||
* @note Uncomment to enable. |
|||
* @default '1024M' |
|||
* @version 4 |
|||
*/ |
|||
//ini_set('memory_limit', '1024M'); |
|||
|
|||
/** |
|||
* Show PHP errors on CLI output. |
|||
* |
|||
* @note Set to '1' for development. |
|||
* @default '0' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('display_errors', '0'); |
|||
|
|||
/** |
|||
* Show startup errors on CLI output. |
|||
* |
|||
* @note Set to '1' for development/debugging. |
|||
* @default '0' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('display_startup_errors', '0'); |
|||
|
|||
/** |
|||
* Type of errors to display. |
|||
* nZEDb code should run clean with no errors, so there is no point to restricting the reported |
|||
* output. Any notices or warnings should be dealt with. |
|||
* It makes no difference to performance if reporting specific types (i.e. strict, warnings, etc.) |
|||
* are disabled, as the error is still prepared just not displayed. |
|||
* |
|||
* @default E_ALL |
|||
* @see https://secure.php.net/manual/en/errorfunc.constants.php |
|||
* @version 4 |
|||
*/ |
|||
ini_set('error_reporting', E_ALL); |
|||
|
|||
/** |
|||
* Turn off HTML tags in error messages. |
|||
* |
|||
* @default '0' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('html_errors', '0'); |
|||
|
|||
/** |
|||
* Set the location to log PHP errors. |
|||
* |
|||
* @default nZEDb_LOGS . 'php_errors.log' |
|||
* @note To log to syslog, put in 'syslog' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('error_log', nZEDb_LOGS . 'php_errors_cli.log'); |
|||
|
|||
/** |
|||
* Log errors to error_log? |
|||
* |
|||
* @default '1' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('log_errors', '1'); |
|||
|
|||
/** |
|||
* Max line length for a error. |
|||
* |
|||
* @default 1024 |
|||
* @version 4 |
|||
*/ |
|||
ini_set('log_errors_max_len', '1024'); |
|||
|
|||
/** |
|||
* Store the last PHP error in $php_errormsg |
|||
* |
|||
* @default '0' |
|||
* @note This is a development/debugging option. |
|||
* @version 4 |
|||
*/ |
|||
ini_set('track_errors', '0'); |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
///////////////////////////////////// PHP Web Settings ///////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
} else { |
|||
|
|||
/** |
|||
* Your server's local timezone. |
|||
* |
|||
* @note Uncomment to enable. |
|||
* @see https://secure.php.net/manual/en/timezones.php |
|||
* @version 4 |
|||
*/ |
|||
//ini_set('date.timezone', 'America/New_York'); |
|||
|
|||
/** |
|||
* Maximum amount of seconds a script can run before being terminated. |
|||
* |
|||
* @default '120' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('max_execution_time', '120'); |
|||
|
|||
/** |
|||
* Maximum amount of memory a PHP script can consume before being terminated. |
|||
* |
|||
* @note Uncomment to enable. |
|||
* @default '1024M' |
|||
* @version 4 |
|||
*/ |
|||
//ini_set('memory_limit', '1024M'); |
|||
|
|||
/** |
|||
* Show PHP errors on web browser. |
|||
* |
|||
* @note Set to '1' for development. |
|||
* @default '0' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('display_errors', '0'); |
|||
|
|||
/** |
|||
* Show startup errors on web browser. |
|||
* |
|||
* @note Set to '1' for development/debugging. |
|||
* @default '0' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('display_startup_errors', '0'); |
|||
|
|||
/** |
|||
* Type of errors to display. |
|||
* nZEDb code should run clean with no errors, so there is no point to restricting the reported |
|||
* output. Any notices or warnings should be dealt with. |
|||
* It makes no difference to performance if reporting specific types (i.e. strict, warnings, etc.) |
|||
* are disabled, as the error is still prepared just not displayed. |
|||
* |
|||
* @default E_ALL |
|||
* @see https://secure.php.net/manual/en/errorfunc.constants.php |
|||
* @version 4 |
|||
*/ |
|||
ini_set('error_reporting', E_ALL); |
|||
|
|||
/** |
|||
* Turn off HTML tags in error messages. |
|||
* |
|||
* @default '1' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('html_errors', '1'); |
|||
|
|||
/** |
|||
* Set the location to log PHP errors. |
|||
* |
|||
* @default nZEDb_LOGS . 'php_errors.log' |
|||
* @note To log to syslog, put in 'syslog' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('error_log', nZEDb_LOGS . 'php_errors_web.log'); |
|||
|
|||
/** |
|||
* Log errors to error_log? |
|||
* |
|||
* @default '1' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('log_errors', '1'); |
|||
|
|||
/** |
|||
* Max line length for a error. |
|||
* |
|||
* @default 1024 |
|||
* @version 4 |
|||
*/ |
|||
ini_set('log_errors_max_len', '1024'); |
|||
|
|||
/** |
|||
* Store the last PHP error in $php_errormsg |
|||
* |
|||
* @default '0' |
|||
* @note This is a development/debugging option. |
|||
* @version 4 |
|||
*/ |
|||
ini_set('track_errors', '0'); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/////////////////////////////////// PHP Xdebug Settings //////////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
if (extension_loaded('xdebug')) { |
|||
|
|||
/** |
|||
* Display colors on xdebug CLI output? |
|||
* 0 - off, 1 - on only if on a TTY with ansi support, 2 - on regardless of TTY or ansi support. |
|||
* |
|||
* @default 0 |
|||
* @version 4 |
|||
*/ |
|||
ini_set('xdebug.cli_color', '0'); |
|||
|
|||
/** |
|||
* Replace PHP's var_dump with xdebug's own? |
|||
* |
|||
* @default '1' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('xdebug.overload_var_dump', '1'); |
|||
|
|||
/** |
|||
* How many items in a array or object to display on var_dump. |
|||
* |
|||
* @note Set to '-1' for no limit. |
|||
* @default '128' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('xdebug.var_display_max_children', '128'); |
|||
|
|||
/** |
|||
* Maximum string length on var_dump. (anything over is truncated) |
|||
* |
|||
* @note Set to '-1' for no limit. |
|||
* @default '512' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('xdebug.var_display_max_data', '512'); |
|||
|
|||
/** |
|||
* How many nested arrays / objects deep to display on var_dump. |
|||
* |
|||
* @note Set to '-1' for no limit. |
|||
* @note Maximum value is '1023' |
|||
* @default '3' |
|||
* @version 4 |
|||
*/ |
|||
ini_set('xdebug.var_display_max_depth', '3'); |
|||
} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/////////////////////////////////// Maintenance Mode Settings ////////////////////////////////////// |
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
/** |
|||
* Should the site be in maintenance mode. |
|||
* If enabled, it will output the selected HTML file added below to front-end pages as well as |
|||
* outputting a properly formatted API error response with the error text "Maintenance Mode" and |
|||
* status code 503. |
|||
* This will prevent nZEDb from making any MySQL/Sphinx/Cache calls so it's good to use this if you |
|||
* need to stop/restart those services. |
|||
* |
|||
* @note If your site uses some form of webserver-level caching, the cache may need to be cleared |
|||
* when coming out of maintenance mode. Otherwise you'll have to wait until it expires to see |
|||
* your proper site again. |
|||
* @default false |
|||
*/ |
|||
\define('MAINTENANCE_MODE_ENABLED', false); |
|||
|
|||
/** |
|||
* The fully qualified absolute path to the maintenance mode HTML file. |
|||
* This should be a fully complete HTML file and NOT a smarty template. The idea of a maintenance |
|||
* file is it should be static HTML with no dependencies on other systems. |
|||
* That way it can safely be displayed without the database running or any other services (except |
|||
* PHP and the webserver of course). |
|||
* |
|||
* @note On my site I ripped the rendered HTML content of my home page and swapped out the center |
|||
* container for a maintenance message. This is a good process. Be sure to remove all |
|||
* references to user data. Links to other site pages can remain intact. |
|||
* @default '' |
|||
*/ |
|||
\define('MAINTENANCE_MODE_HTML_PATH', ''); |
|||
|
|||
/** |
|||
* What IP addresses should be excluded from maintenance mode. |
|||
* |
|||
* Useful if you want to allow admins to access the site while everyone else sees the maintenance |
|||
* message. |
|||
* |
|||
* @note Keep in mind, if your site uses ipv6 you may need to enter your ipv6 address here as well. |
|||
* @default [] |
|||
*/ |
|||
\define('MAINTENANCE_MODE_IP_EXCEPTIONS', []); |
|||
|
|||
/*************************************************************************************************** |
|||
* ///////////////////////////////////////////////////////////////////////////////////////////////// |
|||
* ///////////////////////////////////// Change log //////////////////////////////////////////////// |
|||
* ///////////////////////////////////////////////////////////////////////////////////////////////// |
|||
* 2017-08-09 v5 Add maintainance mode. |
|||
* 2015-10-14 v4 Change defaults html_errors default to 0 |
|||
* 2015-08-26 v4 Add settings for PHP web/CLI SAPI's. |
|||
* Add settings for Xdebug. |
|||
* All new settings start from the "PHP CLI Settings" up to the "Change log", |
|||
* lines ~544 to ~768 |
|||
* 2015-06-11 v3 Add support for APC or APCu extensions for caching data. Search for @version 3 |
|||
* for the changes. |
|||
* 2015-05-10 v2 Update path to find_password_hash_cost.php in comments. Search for @version 2 |
|||
* for the changes. |
|||
* 2015-05-03 v1 Track settings.php.example changes. |
|||
* Add support for quick and low_priority on MySQL DELETE queries. |
|||
* Search for @version 1 in this file to quickly find these additions. |
|||
* ///////////////////////////////////////////////////////////////////////////////////////////////*/ |
@ -1,2 +0,0 @@ |
|||
* |
|||
!.gitignore |
@ -0,0 +1,7 @@ |
|||
<?php |
|||
|
|||
// autoload.php @generated by Composer |
|||
|
|||
require_once __DIR__ . '/composer/autoload_real.php'; |
|||
|
|||
return ComposerAutoloaderInit1962fe7e26a6b5297243dca268e58722::getLoader(); |
@ -0,0 +1,674 @@ |
|||
GNU GENERAL PUBLIC LICENSE |
|||
Version 3, 29 June 2007 |
|||
|
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies |
|||
of this license document, but changing it is not allowed. |
|||
|
|||
Preamble |
|||
|
|||
The GNU General Public License is a free, copyleft license for |
|||
software and other kinds of works. |
|||
|
|||
The licenses for most software and other practical works are designed |
|||
to take away your freedom to share and change the works. By contrast, |
|||
the GNU General Public License is intended to guarantee your freedom to |
|||
share and change all versions of a program--to make sure it remains free |
|||
software for all its users. We, the Free Software Foundation, use the |
|||
GNU General Public License for most of our software; it applies also to |
|||
any other work released this way by its authors. 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 |
|||
them 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 prevent others from denying you |
|||
these rights or asking you to surrender the rights. Therefore, you have |
|||
certain responsibilities if you distribute copies of the software, or if |
|||
you modify it: responsibilities to respect the freedom of others. |
|||
|
|||
For example, if you distribute copies of such a program, whether |
|||
gratis or for a fee, you must pass on to the recipients the same |
|||
freedoms that you received. 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. |
|||
|
|||
Developers that use the GNU GPL protect your rights with two steps: |
|||
(1) assert copyright on the software, and (2) offer you this License |
|||
giving you legal permission to copy, distribute and/or modify it. |
|||
|
|||
For the developers' and authors' protection, the GPL clearly explains |
|||
that there is no warranty for this free software. For both users' and |
|||
authors' sake, the GPL requires that modified versions be marked as |
|||
changed, so that their problems will not be attributed erroneously to |
|||
authors of previous versions. |
|||
|
|||
Some devices are designed to deny users access to install or run |
|||
modified versions of the software inside them, although the manufacturer |
|||
can do so. This is fundamentally incompatible with the aim of |
|||
protecting users' freedom to change the software. The systematic |
|||
pattern of such abuse occurs in the area of products for individuals to |
|||
use, which is precisely where it is most unacceptable. Therefore, we |
|||
have designed this version of the GPL to prohibit the practice for those |
|||
products. If such problems arise substantially in other domains, we |
|||
stand ready to extend this provision to those domains in future versions |
|||
of the GPL, as needed to protect the freedom of users. |
|||
|
|||
Finally, every program is threatened constantly by software patents. |
|||
States should not allow patents to restrict development and use of |
|||
software on general-purpose computers, but in those that do, we wish to |
|||
avoid the special danger that patents applied to a free program could |
|||
make it effectively proprietary. To prevent this, the GPL assures that |
|||
patents cannot be used to render the program non-free. |
|||
|
|||
The precise terms and conditions for copying, distribution and |
|||
modification follow. |
|||
|
|||
TERMS AND CONDITIONS |
|||
|
|||
0. Definitions. |
|||
|
|||
"This License" refers to version 3 of the GNU General Public License. |
|||
|
|||
"Copyright" also means copyright-like laws that apply to other kinds of |
|||
works, such as semiconductor masks. |
|||
|
|||
"The Program" refers to any copyrightable work licensed under this |
|||
License. Each licensee is addressed as "you". "Licensees" and |
|||
"recipients" may be individuals or organizations. |
|||
|
|||
To "modify" a work means to copy from or adapt all or part of the work |
|||
in a fashion requiring copyright permission, other than the making of an |
|||
exact copy. The resulting work is called a "modified version" of the |
|||
earlier work or a work "based on" the earlier work. |
|||
|
|||
A "covered work" means either the unmodified Program or a work based |
|||
on the Program. |
|||
|
|||
To "propagate" a work means to do anything with it that, without |
|||
permission, would make you directly or secondarily liable for |
|||
infringement under applicable copyright law, except executing it on a |
|||
computer or modifying a private copy. Propagation includes copying, |
|||
distribution (with or without modification), making available to the |
|||
public, and in some countries other activities as well. |
|||
|
|||
To "convey" a work means any kind of propagation that enables other |
|||
parties to make or receive copies. Mere interaction with a user through |
|||
a computer network, with no transfer of a copy, is not conveying. |
|||
|
|||
An interactive user interface displays "Appropriate Legal Notices" |
|||
to the extent that it includes a convenient and prominently visible |
|||
feature that (1) displays an appropriate copyright notice, and (2) |
|||
tells the user that there is no warranty for the work (except to the |
|||
extent that warranties are provided), that licensees may convey the |
|||
work under this License, and how to view a copy of this License. If |
|||
the interface presents a list of user commands or options, such as a |
|||
menu, a prominent item in the list meets this criterion. |
|||
|
|||
1. Source Code. |
|||
|
|||
The "source code" for a work means the preferred form of the work |
|||
for making modifications to it. "Object code" means any non-source |
|||
form of a work. |
|||
|
|||
A "Standard Interface" means an interface that either is an official |
|||
standard defined by a recognized standards body, or, in the case of |
|||
interfaces specified for a particular programming language, one that |
|||
is widely used among developers working in that language. |
|||
|
|||
The "System Libraries" of an executable work include anything, other |
|||
than the work as a whole, that (a) is included in the normal form of |
|||
packaging a Major Component, but which is not part of that Major |
|||
Component, and (b) serves only to enable use of the work with that |
|||
Major Component, or to implement a Standard Interface for which an |
|||
implementation is available to the public in source code form. A |
|||
"Major Component", in this context, means a major essential component |
|||
(kernel, window system, and so on) of the specific operating system |
|||
(if any) on which the executable work runs, or a compiler used to |
|||
produce the work, or an object code interpreter used to run it. |
|||
|
|||
The "Corresponding Source" for a work in object code form means all |
|||
the source code needed to generate, install, and (for an executable |
|||
work) run the object code and to modify the work, including scripts to |
|||
control those activities. However, it does not include the work's |
|||
System Libraries, or general-purpose tools or generally available free |
|||
programs which are used unmodified in performing those activities but |
|||
which are not part of the work. For example, Corresponding Source |
|||
includes interface definition files associated with source files for |
|||
the work, and the source code for shared libraries and dynamically |
|||
linked subprograms that the work is specifically designed to require, |
|||
such as by intimate data communication or control flow between those |
|||
subprograms and other parts of the work. |
|||
|
|||
The Corresponding Source need not include anything that users |
|||
can regenerate automatically from other parts of the Corresponding |
|||
Source. |
|||
|
|||
The Corresponding Source for a work in source code form is that |
|||
same work. |
|||
|
|||
2. Basic Permissions. |
|||
|
|||
All rights granted under this License are granted for the term of |
|||
copyright on the Program, and are irrevocable provided the stated |
|||
conditions are met. This License explicitly affirms your unlimited |
|||
permission to run the unmodified Program. The output from running a |
|||
covered work is covered by this License only if the output, given its |
|||
content, constitutes a covered work. This License acknowledges your |
|||
rights of fair use or other equivalent, as provided by copyright law. |
|||
|
|||
You may make, run and propagate covered works that you do not |
|||
convey, without conditions so long as your license otherwise remains |
|||
in force. You may convey covered works to others for the sole purpose |
|||
of having them make modifications exclusively for you, or provide you |
|||
with facilities for running those works, provided that you comply with |
|||
the terms of this License in conveying all material for which you do |
|||
not control copyright. Those thus making or running the covered works |
|||
for you must do so exclusively on your behalf, under your direction |
|||
and control, on terms that prohibit them from making any copies of |
|||
your copyrighted material outside their relationship with you. |
|||
|
|||
Conveying under any other circumstances is permitted solely under |
|||
the conditions stated below. Sublicensing is not allowed; section 10 |
|||
makes it unnecessary. |
|||
|
|||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
|||
|
|||
No covered work shall be deemed part of an effective technological |
|||
measure under any applicable law fulfilling obligations under article |
|||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
|||
similar laws prohibiting or restricting circumvention of such |
|||
measures. |
|||
|
|||
When you convey a covered work, you waive any legal power to forbid |
|||
circumvention of technological measures to the extent such circumvention |
|||
is effected by exercising rights under this License with respect to |
|||
the covered work, and you disclaim any intention to limit operation or |
|||
modification of the work as a means of enforcing, against the work's |
|||
users, your or third parties' legal rights to forbid circumvention of |
|||
technological measures. |
|||
|
|||
4. Conveying Verbatim Copies. |
|||
|
|||
You may convey 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; |
|||
keep intact all notices stating that this License and any |
|||
non-permissive terms added in accord with section 7 apply to the code; |
|||
keep intact all notices of the absence of any warranty; and give all |
|||
recipients a copy of this License along with the Program. |
|||
|
|||
You may charge any price or no price for each copy that you convey, |
|||
and you may offer support or warranty protection for a fee. |
|||
|
|||
5. Conveying Modified Source Versions. |
|||
|
|||
You may convey a work based on the Program, or the modifications to |
|||
produce it from the Program, in the form of source code under the |
|||
terms of section 4, provided that you also meet all of these conditions: |
|||
|
|||
a) The work must carry prominent notices stating that you modified |
|||
it, and giving a relevant date. |
|||
|
|||
b) The work must carry prominent notices stating that it is |
|||
released under this License and any conditions added under section |
|||
7. This requirement modifies the requirement in section 4 to |
|||
"keep intact all notices". |
|||
|
|||
c) You must license the entire work, as a whole, under this |
|||
License to anyone who comes into possession of a copy. This |
|||
License will therefore apply, along with any applicable section 7 |
|||
additional terms, to the whole of the work, and all its parts, |
|||
regardless of how they are packaged. This License gives no |
|||
permission to license the work in any other way, but it does not |
|||
invalidate such permission if you have separately received it. |
|||
|
|||
d) If the work has interactive user interfaces, each must display |
|||
Appropriate Legal Notices; however, if the Program has interactive |
|||
interfaces that do not display Appropriate Legal Notices, your |
|||
work need not make them do so. |
|||
|
|||
A compilation of a covered work with other separate and independent |
|||
works, which are not by their nature extensions of the covered work, |
|||
and which are not combined with it such as to form a larger program, |
|||
in or on a volume of a storage or distribution medium, is called an |
|||
"aggregate" if the compilation and its resulting copyright are not |
|||
used to limit the access or legal rights of the compilation's users |
|||
beyond what the individual works permit. Inclusion of a covered work |
|||
in an aggregate does not cause this License to apply to the other |
|||
parts of the aggregate. |
|||
|
|||
6. Conveying Non-Source Forms. |
|||
|
|||
You may convey a covered work in object code form under the terms |
|||
of sections 4 and 5, provided that you also convey the |
|||
machine-readable Corresponding Source under the terms of this License, |
|||
in one of these ways: |
|||
|
|||
a) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by the |
|||
Corresponding Source fixed on a durable physical medium |
|||
customarily used for software interchange. |
|||
|
|||
b) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by a |
|||
written offer, valid for at least three years and valid for as |
|||
long as you offer spare parts or customer support for that product |
|||
model, to give anyone who possesses the object code either (1) a |
|||
copy of the Corresponding Source for all the software in the |
|||
product that is covered by this License, on a durable physical |
|||
medium customarily used for software interchange, for a price no |
|||
more than your reasonable cost of physically performing this |
|||
conveying of source, or (2) access to copy the |
|||
Corresponding Source from a network server at no charge. |
|||
|
|||
c) Convey individual copies of the object code with a copy of the |
|||
written offer to provide the Corresponding Source. This |
|||
alternative is allowed only occasionally and noncommercially, and |
|||
only if you received the object code with such an offer, in accord |
|||
with subsection 6b. |
|||
|
|||
d) Convey the object code by offering access from a designated |
|||
place (gratis or for a charge), and offer equivalent access to the |
|||
Corresponding Source in the same way through the same place at no |
|||
further charge. You need not require recipients to copy the |
|||
Corresponding Source along with the object code. If the place to |
|||
copy the object code is a network server, the Corresponding Source |
|||
may be on a different server (operated by you or a third party) |
|||
that supports equivalent copying facilities, provided you maintain |
|||
clear directions next to the object code saying where to find the |
|||
Corresponding Source. Regardless of what server hosts the |
|||
Corresponding Source, you remain obligated to ensure that it is |
|||
available for as long as needed to satisfy these requirements. |
|||
|
|||
e) Convey the object code using peer-to-peer transmission, provided |
|||
you inform other peers where the object code and Corresponding |
|||
Source of the work are being offered to the general public at no |
|||
charge under subsection 6d. |
|||
|
|||
A separable portion of the object code, whose source code is excluded |
|||
from the Corresponding Source as a System Library, need not be |
|||
included in conveying the object code work. |
|||
|
|||
A "User Product" is either (1) a "consumer product", which means any |
|||
tangible personal property which is normally used for personal, family, |
|||
or household purposes, or (2) anything designed or sold for incorporation |
|||
into a dwelling. In determining whether a product is a consumer product, |
|||
doubtful cases shall be resolved in favor of coverage. For a particular |
|||
product received by a particular user, "normally used" refers to a |
|||
typical or common use of that class of product, regardless of the status |
|||
of the particular user or of the way in which the particular user |
|||
actually uses, or expects or is expected to use, the product. A product |
|||
is a consumer product regardless of whether the product has substantial |
|||
commercial, industrial or non-consumer uses, unless such uses represent |
|||
the only significant mode of use of the product. |
|||
|
|||
"Installation Information" for a User Product means any methods, |
|||
procedures, authorization keys, or other information required to install |
|||
and execute modified versions of a covered work in that User Product from |
|||
a modified version of its Corresponding Source. The information must |
|||
suffice to ensure that the continued functioning of the modified object |
|||
code is in no case prevented or interfered with solely because |
|||
modification has been made. |
|||
|
|||
If you convey an object code work under this section in, or with, or |
|||
specifically for use in, a User Product, and the conveying occurs as |
|||
part of a transaction in which the right of possession and use of the |
|||
User Product is transferred to the recipient in perpetuity or for a |
|||
fixed term (regardless of how the transaction is characterized), the |
|||
Corresponding Source conveyed under this section must be accompanied |
|||
by the Installation Information. But this requirement does not apply |
|||
if neither you nor any third party retains the ability to install |
|||
modified object code on the User Product (for example, the work has |
|||
been installed in ROM). |
|||
|
|||
The requirement to provide Installation Information does not include a |
|||
requirement to continue to provide support service, warranty, or updates |
|||
for a work that has been modified or installed by the recipient, or for |
|||
the User Product in which it has been modified or installed. Access to a |
|||
network may be denied when the modification itself materially and |
|||
adversely affects the operation of the network or violates the rules and |
|||
protocols for communication across the network. |
|||
|
|||
Corresponding Source conveyed, and Installation Information provided, |
|||
in accord with this section must be in a format that is publicly |
|||
documented (and with an implementation available to the public in |
|||
source code form), and must require no special password or key for |
|||
unpacking, reading or copying. |
|||
|
|||
7. Additional Terms. |
|||
|
|||
"Additional permissions" are terms that supplement the terms of this |
|||
License by making exceptions from one or more of its conditions. |
|||
Additional permissions that are applicable to the entire Program shall |
|||
be treated as though they were included in this License, to the extent |
|||
that they are valid under applicable law. If additional permissions |
|||
apply only to part of the Program, that part may be used separately |
|||
under those permissions, but the entire Program remains governed by |
|||
this License without regard to the additional permissions. |
|||
|
|||
When you convey a copy of a covered work, you may at your option |
|||
remove any additional permissions from that copy, or from any part of |
|||
it. (Additional permissions may be written to require their own |
|||
removal in certain cases when you modify the work.) You may place |
|||
additional permissions on material, added by you to a covered work, |
|||
for which you have or can give appropriate copyright permission. |
|||
|
|||
Notwithstanding any other provision of this License, for material you |
|||
add to a covered work, you may (if authorized by the copyright holders of |
|||
that material) supplement the terms of this License with terms: |
|||
|
|||
a) Disclaiming warranty or limiting liability differently from the |
|||
terms of sections 15 and 16 of this License; or |
|||
|
|||
b) Requiring preservation of specified reasonable legal notices or |
|||
author attributions in that material or in the Appropriate Legal |
|||
Notices displayed by works containing it; or |
|||
|
|||
c) Prohibiting misrepresentation of the origin of that material, or |
|||
requiring that modified versions of such material be marked in |
|||
reasonable ways as different from the original version; or |
|||
|
|||
d) Limiting the use for publicity purposes of names of licensors or |
|||
authors of the material; or |
|||
|
|||
e) Declining to grant rights under trademark law for use of some |
|||
trade names, trademarks, or service marks; or |
|||
|
|||
f) Requiring indemnification of licensors and authors of that |
|||
material by anyone who conveys the material (or modified versions of |
|||
it) with contractual assumptions of liability to the recipient, for |
|||
any liability that these contractual assumptions directly impose on |
|||
those licensors and authors. |
|||
|
|||
All other non-permissive additional terms are considered "further |
|||
restrictions" within the meaning of section 10. If the Program as you |
|||
received it, or any part of it, contains a notice stating that it is |
|||
governed by this License along with a term that is a further |
|||
restriction, you may remove that term. If a license document contains |
|||
a further restriction but permits relicensing or conveying under this |
|||
License, you may add to a covered work material governed by the terms |
|||
of that license document, provided that the further restriction does |
|||
not survive such relicensing or conveying. |
|||
|
|||
If you add terms to a covered work in accord with this section, you |
|||
must place, in the relevant source files, a statement of the |
|||
additional terms that apply to those files, or a notice indicating |
|||
where to find the applicable terms. |
|||
|
|||
Additional terms, permissive or non-permissive, may be stated in the |
|||
form of a separately written license, or stated as exceptions; |
|||
the above requirements apply either way. |
|||
|
|||
8. Termination. |
|||
|
|||
You may not propagate or modify a covered work except as expressly |
|||
provided under this License. Any attempt otherwise to propagate or |
|||
modify it is void, and will automatically terminate your rights under |
|||
this License (including any patent licenses granted under the third |
|||
paragraph of section 11). |
|||
|
|||
However, if you cease all violation of this License, then your |
|||
license from a particular copyright holder is reinstated (a) |
|||
provisionally, unless and until the copyright holder explicitly and |
|||
finally terminates your license, and (b) permanently, if the copyright |
|||
holder fails to notify you of the violation by some reasonable means |
|||
prior to 60 days after the cessation. |
|||
|
|||
Moreover, your license from a particular copyright holder is |
|||
reinstated permanently if the copyright holder notifies you of the |
|||
violation by some reasonable means, this is the first time you have |
|||
received notice of violation of this License (for any work) from that |
|||
copyright holder, and you cure the violation prior to 30 days after |
|||
your receipt of the notice. |
|||
|
|||
Termination of your rights under this section does not terminate the |
|||
licenses of parties who have received copies or rights from you under |
|||
this License. If your rights have been terminated and not permanently |
|||
reinstated, you do not qualify to receive new licenses for the same |
|||
material under section 10. |
|||
|
|||
9. Acceptance Not Required for Having Copies. |
|||
|
|||
You are not required to accept this License in order to receive or |
|||
run a copy of the Program. Ancillary propagation of a covered work |
|||
occurring solely as a consequence of using peer-to-peer transmission |
|||
to receive a copy likewise does not require acceptance. However, |
|||
nothing other than this License grants you permission to propagate or |
|||
modify any covered work. These actions infringe copyright if you do |
|||
not accept this License. Therefore, by modifying or propagating a |
|||
covered work, you indicate your acceptance of this License to do so. |
|||
|
|||
10. Automatic Licensing of Downstream Recipients. |
|||
|
|||
Each time you convey a covered work, the recipient automatically |
|||
receives a license from the original licensors, to run, modify and |
|||
propagate that work, subject to this License. You are not responsible |
|||
for enforcing compliance by third parties with this License. |
|||
|
|||
An "entity transaction" is a transaction transferring control of an |
|||
organization, or substantially all assets of one, or subdividing an |
|||
organization, or merging organizations. If propagation of a covered |
|||
work results from an entity transaction, each party to that |
|||
transaction who receives a copy of the work also receives whatever |
|||
licenses to the work the party's predecessor in interest had or could |
|||
give under the previous paragraph, plus a right to possession of the |
|||
Corresponding Source of the work from the predecessor in interest, if |
|||
the predecessor has it or can get it with reasonable efforts. |
|||
|
|||
You may not impose any further restrictions on the exercise of the |
|||
rights granted or affirmed under this License. For example, you may |
|||
not impose a license fee, royalty, or other charge for exercise of |
|||
rights granted under this License, and you may not initiate litigation |
|||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
|||
any patent claim is infringed by making, using, selling, offering for |
|||
sale, or importing the Program or any portion of it. |
|||
|
|||
11. Patents. |
|||
|
|||
A "contributor" is a copyright holder who authorizes use under this |
|||
License of the Program or a work on which the Program is based. The |
|||
work thus licensed is called the contributor's "contributor version". |
|||
|
|||
A contributor's "essential patent claims" are all patent claims |
|||
owned or controlled by the contributor, whether already acquired or |
|||
hereafter acquired, that would be infringed by some manner, permitted |
|||
by this License, of making, using, or selling its contributor version, |
|||
but do not include claims that would be infringed only as a |
|||
consequence of further modification of the contributor version. For |
|||
purposes of this definition, "control" includes the right to grant |
|||
patent sublicenses in a manner consistent with the requirements of |
|||
this License. |
|||
|
|||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
|||
patent license under the contributor's essential patent claims, to |
|||
make, use, sell, offer for sale, import and otherwise run, modify and |
|||
propagate the contents of its contributor version. |
|||
|
|||
In the following three paragraphs, a "patent license" is any express |
|||
agreement or commitment, however denominated, not to enforce a patent |
|||
(such as an express permission to practice a patent or covenant not to |
|||
sue for patent infringement). To "grant" such a patent license to a |
|||
party means to make such an agreement or commitment not to enforce a |
|||
patent against the party. |
|||
|
|||
If you convey a covered work, knowingly relying on a patent license, |
|||
and the Corresponding Source of the work is not available for anyone |
|||
to copy, free of charge and under the terms of this License, through a |
|||
publicly available network server or other readily accessible means, |
|||
then you must either (1) cause the Corresponding Source to be so |
|||
available, or (2) arrange to deprive yourself of the benefit of the |
|||
patent license for this particular work, or (3) arrange, in a manner |
|||
consistent with the requirements of this License, to extend the patent |
|||
license to downstream recipients. "Knowingly relying" means you have |
|||
actual knowledge that, but for the patent license, your conveying the |
|||
covered work in a country, or your recipient's use of the covered work |
|||
in a country, would infringe one or more identifiable patents in that |
|||
country that you have reason to believe are valid. |
|||
|
|||
If, pursuant to or in connection with a single transaction or |
|||
arrangement, you convey, or propagate by procuring conveyance of, a |
|||
covered work, and grant a patent license to some of the parties |
|||
receiving the covered work authorizing them to use, propagate, modify |
|||
or convey a specific copy of the covered work, then the patent license |
|||
you grant is automatically extended to all recipients of the covered |
|||
work and works based on it. |
|||
|
|||
A patent license is "discriminatory" if it does not include within |
|||
the scope of its coverage, prohibits the exercise of, or is |
|||
conditioned on the non-exercise of one or more of the rights that are |
|||
specifically granted under this License. You may not convey a covered |
|||
work if you are a party to an arrangement with a third party that is |
|||
in the business of distributing software, under which you make payment |
|||
to the third party based on the extent of your activity of conveying |
|||
the work, and under which the third party grants, to any of the |
|||
parties who would receive the covered work from you, a discriminatory |
|||
patent license (a) in connection with copies of the covered work |
|||
conveyed by you (or copies made from those copies), or (b) primarily |
|||
for and in connection with specific products or compilations that |
|||
contain the covered work, unless you entered into that arrangement, |
|||
or that patent license was granted, prior to 28 March 2007. |
|||
|
|||
Nothing in this License shall be construed as excluding or limiting |
|||
any implied license or other defenses to infringement that may |
|||
otherwise be available to you under applicable patent law. |
|||
|
|||
12. No Surrender of Others' Freedom. |
|||
|
|||
If 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 convey a |
|||
covered work so as to satisfy simultaneously your obligations under this |
|||
License and any other pertinent obligations, then as a consequence you may |
|||
not convey it at all. For example, if you agree to terms that obligate you |
|||
to collect a royalty for further conveying from those to whom you convey |
|||
the Program, the only way you could satisfy both those terms and this |
|||
License would be to refrain entirely from conveying the Program. |
|||
|
|||
13. Use with the GNU Affero General Public License. |
|||
|
|||
Notwithstanding any other provision of this License, you have |
|||
permission to link or combine any covered work with a work licensed |
|||
under version 3 of the GNU Affero General Public License into a single |
|||
combined work, and to convey the resulting work. The terms of this |
|||
License will continue to apply to the part which is the covered work, |
|||
but the special requirements of the GNU Affero General Public License, |
|||
section 13, concerning interaction through a network will apply to the |
|||
combination as such. |
|||
|
|||
14. Revised Versions of this License. |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions of |
|||
the GNU 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 that a certain numbered version of the GNU General |
|||
Public License "or any later version" applies to it, you have the |
|||
option of following the terms and conditions either of that numbered |
|||
version or of any later version published by the Free Software |
|||
Foundation. If the Program does not specify a version number of the |
|||
GNU General Public License, you may choose any version ever published |
|||
by the Free Software Foundation. |
|||
|
|||
If the Program specifies that a proxy can decide which future |
|||
versions of the GNU General Public License can be used, that proxy's |
|||
public statement of acceptance of a version permanently authorizes you |
|||
to choose that version for the Program. |
|||
|
|||
Later license versions may give you additional or different |
|||
permissions. However, no additional obligations are imposed on any |
|||
author or copyright holder as a result of your choosing to follow a |
|||
later version. |
|||
|
|||
15. Disclaimer of Warranty. |
|||
|
|||
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. |
|||
|
|||
16. Limitation of Liability. |
|||
|
|||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
|||
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. |
|||
|
|||
17. Interpretation of Sections 15 and 16. |
|||
|
|||
If the disclaimer of warranty and limitation of liability provided |
|||
above cannot be given local legal effect according to their terms, |
|||
reviewing courts shall apply local law that most closely approximates |
|||
an absolute waiver of all civil liability in connection with the |
|||
Program, unless a warranty or assumption of liability accompanies a |
|||
copy of the Program in return for a fee. |
|||
|
|||
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 |
|||
state 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 3 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, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Also add information on how to contact you by electronic and paper mail. |
|||
|
|||
If the program does terminal interaction, make it output a short |
|||
notice like this when it starts in an interactive mode: |
|||
|
|||
{project} Copyright (C) {year} {fullname} |
|||
This program 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, your program's commands |
|||
might be different; for a GUI interface, you would use an "about box". |
|||
|
|||
You should also get your employer (if you work as a programmer) or school, |
|||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
|||
For more information on this, and how to apply and follow the GNU GPL, see |
|||
<http://www.gnu.org/licenses/>. |
|||
|
|||
The GNU 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. But first, please read |
|||
<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
@ -0,0 +1,39 @@ |
|||
# Steamfront - PHP Wrapper for Accessing the Steam Storefront API |
|||
|
|||
This is a simple wrapper for accessing the very lightweight API that Steam offers for its Big Picture Mode. |
|||
It provides detailed application information, without being constrained to a particular Steam user's library like their |
|||
Web API. |
|||
|
|||
This makes it especially useful for referencing large amount of information in which you have the Steam App ID |
|||
readily available. |
|||
|
|||
You can either use the Includes.php file and do a manual clone, or you can do a composer based install and use autoloading |
|||
namespaces. |
|||
|
|||
Once you've got it running (autoload used below), simply invoke the client and perform a sample query: |
|||
|
|||
```php |
|||
<?php |
|||
|
|||
use b3rs3rk\steamfront\Main; |
|||
|
|||
$client = new b3rs3rk\steamfront\Main( |
|||
[ |
|||
'country_code' => 'us', |
|||
'local_lang' => 'english' |
|||
] |
|||
); |
|||
|
|||
$data = $client->getFeaturedApps(); |
|||
|
|||
print_r($data); |
|||
|
|||
exit; |
|||
``` |
|||
|
|||
API framework was footprinted from [here](https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI). |
|||
|
|||
Http::get function was a slight rip off of |
|||
[@Moinax's](https://github.com/Moinax/TvDb/blob/master/src/Moinax/TvDb/Http/CurlClient.php#L14) function. |
|||
|
|||
Enjoy - b3rs3rk |
@ -0,0 +1,28 @@ |
|||
{ |
|||
"name": "b3rs3rk/steamfront", |
|||
"description": "PHP Wrapper for Accessing the Steam Storefront API", |
|||
"minimum-stability": "stable", |
|||
"license": "GPL-3.0", |
|||
"authors": [ |
|||
{ |
|||
"name": "b3rs3rk", |
|||
"email": "b3rs3rk@protonmail.com" |
|||
} |
|||
], |
|||
"autoload": { |
|||
"psr-0": { |
|||
"b3rs3rk\\": "source" |
|||
} |
|||
}, |
|||
"require": { |
|||
"php": ">=5.6.0", |
|||
"ext-curl": "*", |
|||
"ext-json": "*" |
|||
}, |
|||
"repositories": [ |
|||
{ |
|||
"type": "vcs", |
|||
"url": "https://github.com/b3rs3rk/steamfront" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,171 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
namespace b3rs3rk\steamfront; |
|||
|
|||
use b3rs3rk\steamfront\http\Http; |
|||
use b3rs3rk\steamfront\data\App; |
|||
|
|||
/** |
|||
* Class SteamFront |
|||
* |
|||
* @package b3rs3rk\steamfront |
|||
*/ |
|||
class Main |
|||
{ |
|||
/** |
|||
* Top domain URL of the Steam API |
|||
*/ |
|||
const STEAM_API_ROOT = 'https://api.steampowered.com/'; |
|||
|
|||
/** |
|||
* Top domain URL of the Steam API |
|||
*/ |
|||
const STEAM_STORE_ROOT = 'https://store.steampowered.com/'; |
|||
|
|||
/** |
|||
* Requested return format for full list |
|||
*/ |
|||
const STEAM_API_RESP_TYPE = '?format=json'; |
|||
|
|||
/** |
|||
* Path to the JSON encoded list of all Steam AppIDs |
|||
*/ |
|||
const APPS_FULL_LIST_PATH = 'ISteamApps/GetAppList/v2/'; |
|||
|
|||
/** |
|||
* Path to featured apps request |
|||
*/ |
|||
const FEATURED_APPS_PATH = 'api/featured/'; |
|||
|
|||
/** |
|||
* Path to featured categories request'; |
|||
*/ |
|||
const FEATURED_CATS_PATH = 'api/featuredcategories/'; |
|||
|
|||
/** |
|||
* Path to App details request |
|||
*/ |
|||
const APP_DETAILS_PATH = 'api/appdetails?appids='; |
|||
|
|||
/** |
|||
* @var string The two letter country code from which to retrieve localized currency information |
|||
*/ |
|||
protected $countryCode; |
|||
|
|||
/** |
|||
* @var string The language (in English) from which to retrieve localized strings e.g. German, Italian |
|||
*/ |
|||
protected $localLang; |
|||
|
|||
/** |
|||
* Main constructor. |
|||
* |
|||
* @param array $options |
|||
*/ |
|||
public function __construct(array $options = array()) |
|||
{ |
|||
$defaults = [ |
|||
'country_code' => 'us', |
|||
'local_lang' => 'english', |
|||
]; |
|||
$options += $defaults; |
|||
|
|||
$this->countryCode = '&cc=' . $options['country_code']; |
|||
$this->localLang = '&l=' . $options['local_lang']; |
|||
} |
|||
|
|||
/** |
|||
* Gets requested data using Http client and returns the json decoded response |
|||
* |
|||
* @param string $root The root url of the request |
|||
* @param string $path The path of the request |
|||
* |
|||
* @return array|bool The JSON decoded response or false |
|||
*/ |
|||
public function get($root, $path) |
|||
{ |
|||
$url = $root . $path; |
|||
|
|||
$response = Http::get($url . $this->countryCode . $this->localLang); |
|||
|
|||
if (is_array($response)) { |
|||
|
|||
return $response; |
|||
} else { |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Retrieves full Steam library info and returns in JSON decoded format |
|||
* |
|||
* @return array Steam library full app listing decoded JSON response |
|||
*/ |
|||
public function getFullAppList() |
|||
{ |
|||
return $this->get(self::STEAM_API_ROOT, self::APPS_FULL_LIST_PATH . self::STEAM_API_RESP_TYPE); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves Featured Steam Games Info and returns in JSON decoded format |
|||
* |
|||
* @return array Featured applications -- no manipulation |
|||
*/ |
|||
public function getFeaturedApps() |
|||
{ |
|||
return $this->get(self::STEAM_STORE_ROOT, self::FEATURED_APPS_PATH); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves Featured Steam Games Info and returns in JSON decoded format |
|||
* |
|||
* @return array|bool |
|||
*/ |
|||
public function getFeaturedCategories() |
|||
{ |
|||
return $this->get(self::STEAM_STORE_ROOT, self::FEATURED_CATS_PATH); |
|||
} |
|||
|
|||
/** |
|||
* Retrieves App information for a specific AppID |
|||
* |
|||
* @param int $id The id argument settings value for the API query |
|||
* @param string $filter The filter argument settings value for the API query |
|||
* |
|||
* @return false|App The normalized data return for the requested Steam Application ID |
|||
*/ |
|||
public function getAppDetails($id, $filter = '') |
|||
{ |
|||
if (!empty($filter)) { |
|||
$filter = '&filters=' . $filter; |
|||
} |
|||
|
|||
$app = $this->get(self::STEAM_STORE_ROOT, self::APP_DETAILS_PATH . $id . $filter); |
|||
|
|||
if ($app[$id]['success'] == true) { |
|||
return new App($app[$id]['data']); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,299 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
namespace b3rs3rk\steamfront\data; |
|||
|
|||
/** |
|||
* Class App - provides object oriented response mapping for Steam Apps |
|||
* |
|||
* @package b3rs3rk\steamfront\data |
|||
*/ |
|||
class App |
|||
{ |
|||
/** |
|||
* @var int ID of the App |
|||
*/ |
|||
public $appid; |
|||
|
|||
/** |
|||
* @var string Name of App |
|||
*/ |
|||
public $name; |
|||
|
|||
/** |
|||
* @var string Type of App |
|||
*/ |
|||
public $type; |
|||
|
|||
/** |
|||
* @var string Date of release |
|||
*/ |
|||
public $releasedate; |
|||
|
|||
/** |
|||
* @var int Age Requirement |
|||
*/ |
|||
public $requiredage; |
|||
|
|||
/** |
|||
* @var string Supported in-app languages |
|||
*/ |
|||
public $languages; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $description; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $developers; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $publishers; |
|||
|
|||
/** |
|||
* @var string Original app's website |
|||
*/ |
|||
public $website; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $supportinfo; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $platforms; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $requirements; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $pricing; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $metacritic; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $categories; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $genres; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $images; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $achievements; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
public $recommendations; |
|||
|
|||
/** |
|||
* App constructor. Calls all sub-functions to fill class variables |
|||
* |
|||
* @param array $data |
|||
*/ |
|||
public function __construct(array $data) |
|||
{ |
|||
$this->data = $data; |
|||
|
|||
$this->setCritical(); |
|||
$this->setRatings(); |
|||
$this->setDemographics(); |
|||
$this->setDescriptions(); |
|||
$this->setSpecifications(); |
|||
$this->setClassifications(); |
|||
$this->setImages(); |
|||
$this->setPricing(); |
|||
|
|||
unset($this->data); // Unset $data or raw response is returned with class object |
|||
} |
|||
|
|||
/** |
|||
* Sets absolutely critical info |
|||
*/ |
|||
protected function setCritical() |
|||
{ |
|||
$this->setNonMatching($this->appid, 'steam_appid'); |
|||
$this->setMatching('type'); |
|||
$this->setMatching('name'); |
|||
$this->setNonMatching($this->releasedate, 'release_date'); |
|||
} |
|||
|
|||
/** |
|||
* Assigns ratings information to the data return |
|||
*/ |
|||
protected function setRatings() |
|||
{ |
|||
$this->setMatching('metacritic'); |
|||
$this->setMatching('recommendations'); |
|||
} |
|||
|
|||
/** |
|||
* Assigns demographics information to the data return |
|||
*/ |
|||
protected function setDemographics() |
|||
{ |
|||
$this->setNonMatching($this->requiredage, 'required_age'); |
|||
$this->setNonMatching($this->languages, 'supoorted_languages'); |
|||
} |
|||
|
|||
/** |
|||
* Assigns authoring information to the data return |
|||
*/ |
|||
protected function setAuthoring() |
|||
{ |
|||
$this->setMatching('developers'); |
|||
$this->setMatching('publishers'); |
|||
$this->setMatching('website'); |
|||
$this->setNonMatching($this->supportinfo, 'support_info'); |
|||
} |
|||
|
|||
/** |
|||
* Assigns descriptions to the data return |
|||
*/ |
|||
protected function setDescriptions() |
|||
{ |
|||
$keys = [ |
|||
'detailed' => 'detailed_description', |
|||
'short' => 'short_description', |
|||
'about' => 'about_the_game', |
|||
]; |
|||
|
|||
$this->setVarWithArray($this->description, $keys); |
|||
} |
|||
|
|||
/** |
|||
* Assigns specifications to the data return |
|||
*/ |
|||
protected function setSpecifications() |
|||
{ |
|||
$this->setMatching('platforms'); |
|||
|
|||
$keys = [ |
|||
'pc' => 'pc_requirements', |
|||
'mac' => 'mac_requirements', |
|||
'linux' => 'linux_requirements', |
|||
]; |
|||
|
|||
$this->setVarWithArray($this->requirements, $keys); |
|||
} |
|||
|
|||
/** |
|||
* Assigns classification to the data return |
|||
*/ |
|||
protected function setClassifications() |
|||
{ |
|||
$this->setMatching('categories'); |
|||
$this->setMatching('genres'); |
|||
} |
|||
|
|||
/** |
|||
* Assigns all imaged based information to one sub-key in the return |
|||
*/ |
|||
protected function setImages() |
|||
{ |
|||
$keys = [ |
|||
'header' => 'header_image', |
|||
'background' => 'background', |
|||
'screenshots' => 'screenshots', |
|||
]; |
|||
|
|||
$this->setVarWithArray($this->images, $keys); |
|||
} |
|||
|
|||
/** |
|||
* Assigns the pricing information to the data in the return |
|||
*/ |
|||
protected function setPricing() |
|||
{ |
|||
$this->setNonMatching($this->pricing, 'price_overview'); |
|||
} |
|||
|
|||
/** |
|||
* Sets the named class value to the differently named key |
|||
* |
|||
* @var $this->$classvar The class variable to set |
|||
* @param string $key The non matching key to retrieve the data from |
|||
*/ |
|||
protected function setNonMatching(&$classVar, string $key) |
|||
{ |
|||
if (isset($this->data[$key])) { |
|||
$classVar = $this->data[$key]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets the class value to the key value with the same key name |
|||
* |
|||
* @param string $key The matching key data to class value to set |
|||
*/ |
|||
protected function setMatching(string $key) |
|||
{ |
|||
if (isset($this->data[$key])) { |
|||
$this->$key = $this->data[$key]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Sets an array type class variable based on an associative array of keys as their values |
|||
* Checks each key to make sure it exists and strips html tags from API response to normalize data |
|||
* |
|||
* @var $this->$classvar The class variable to set |
|||
* @param array $slice The array of data keys and values to match |
|||
*/ |
|||
protected function setVarWithArray(&$classVar, array $slice) |
|||
{ |
|||
foreach($slice AS $key => $value) { |
|||
if (isset($this->data[$value])) { |
|||
$keydata = $this->data[$value]; |
|||
if (is_array($keydata)) { |
|||
array_walk_recursive($keydata, 'strip_tags'); |
|||
} else { |
|||
$keydata = strip_tags($keydata); |
|||
} |
|||
$classVar[$key] = $keydata; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
namespace b3rs3rk\steamfront\http; |
|||
|
|||
/** |
|||
* Class Http |
|||
* |
|||
* @package b3rs3rk\steamfront\http |
|||
*/ |
|||
class Http |
|||
{ |
|||
/** |
|||
* @param string $url The url cUrl should get |
|||
* |
|||
* @return string|array The cUrl response data -- if JSON detected, decode first |
|||
* @throws HttpException Thrown if not 200 OK |
|||
*/ |
|||
public static function get(string $url) |
|||
{ |
|||
$ch = curl_init($url); |
|||
|
|||
curl_setopt_array( |
|||
$ch, |
|||
[ |
|||
CURLOPT_HEADER => 1, |
|||
CURLOPT_RETURNTRANSFER => 1, |
|||
CURLOPT_SSL_VERIFYPEER => 0, |
|||
] |
|||
); |
|||
|
|||
$response = curl_exec($ch); |
|||
|
|||
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
|||
if ($responseCode != 200) { |
|||
throw new HttpException('Could not reach ' . $url, $responseCode); |
|||
} |
|||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); |
|||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); |
|||
|
|||
curl_close($ch); |
|||
|
|||
$response = substr($response, $headerSize); |
|||
|
|||
if (is_null($response)) { |
|||
throw new HttpException('You are exceeding the API request limit. Please wait longer between requests.', 400); |
|||
} |
|||
|
|||
if(stripos($contentType, 'application/json') !== false) { |
|||
$response = json_decode($response, true); |
|||
} |
|||
|
|||
return $response; |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
namespace b3rs3rk\steamfront\http; |
|||
|
|||
class HttpException extends \Exception {} |
@ -0,0 +1,55 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
include (__DIR__ . '/Includes.php'); |
|||
|
|||
$client = new b3rs3rk\steamfront\Main(); |
|||
|
|||
$testAppID = isset($argv[1]) && is_numeric($argv[1]) ? $argv[1] : 30; // Default is Day of Defeat |
|||
|
|||
$test = $client->getAppDetails($testAppID); |
|||
|
|||
if (!$test instanceof b3rs3rk\steamfront\data\App) { |
|||
if (isset($argv[2]) && $argv[2] === 'debug') { |
|||
var_dump($test); |
|||
} |
|||
throw new Exception("Bad response from API for AppID: {$testAppID}", 1); |
|||
} |
|||
|
|||
$test = $client->getFeaturedApps(); |
|||
|
|||
if (!is_array($test) && !empty($test)) { |
|||
if (isset($argv[2]) && $argv[2] === 'debug') { |
|||
var_dump($test); |
|||
} |
|||
throw new Exception("Bad response from API for Featured App List", 2); |
|||
} |
|||
|
|||
$test = $client->getFeaturedCategories(); |
|||
|
|||
if (!is_array($test) && !empty($test)) { |
|||
if (isset($argv[2]) && $argv[2] === 'debug') { |
|||
var_dump($test); |
|||
} |
|||
throw new Exception("Bad response from API for Featured Categories", 3); |
|||
} |
|||
|
|||
exit('All tests completed successfully.' . PHP_EOL . PHP_EOL); |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
/** |
|||
* 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 3 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 (see LICENSE.txt in the base directory. If |
|||
* not, see: |
|||
* |
|||
* @link <http://www.gnu.org/licenses/>. |
|||
* @author b3rs3rk |
|||
* @copyright 2016 |
|||
*/ |
|||
|
|||
$path = '/../source/b3rs3rk/steamfront/'; |
|||
|
|||
//Main Includes |
|||
require_once (__DIR__ . $path . 'Main.php'); |
|||
|
|||
//Data Includes |
|||
require_once (__DIR__ . $path . '/data/App.php'); |
|||
|
|||
//HTTP Includes |
|||
require_once (__DIR__ . $path . '/http/Http.php'); |
|||
require_once (__DIR__ . $path . '/http/HttpException.php'); |
@ -0,0 +1,21 @@ |
|||
exclude_paths: |
|||
- ".phpcs/**/*" |
|||
- "examples/**/*" |
|||
engines: |
|||
phpmd: |
|||
enabled: false |
|||
config: |
|||
file_extensions: "php" |
|||
rulesets: "unusedcode" |
|||
phpcodesniffer: |
|||
enabled: true |
|||
config: |
|||
file_extensions: "php" |
|||
standard: "/code/.phpcs/Barracuda/ruleset.xml" |
|||
ignore_warnings: true |
|||
fixme: |
|||
enabled: true |
|||
ratings: |
|||
paths: |
|||
- "*.php" |
|||
- "**/*.php" |
@ -0,0 +1,70 @@ |
|||
<?php |
|||
/** |
|||
* This sniff prohibits the use of single line multi line comments |
|||
* |
|||
* PHP version 5 |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Ryan Matthews <rmatthews@barracuda.com> |
|||
* @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence |
|||
* @version 1.0.00 |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
|
|||
/** |
|||
* This sniff prohibits the use of Perl style hash comments. |
|||
* |
|||
* An example of a hash comment is: |
|||
* |
|||
* <code> |
|||
* /* This is a single line multi line comment, which is prohibited. */ |
|||
/* $hello = 'hello'; |
|||
* </code> |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Ryan Matthews <rmatthews@barracuda.com> |
|||
* @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence |
|||
* @version Release: 1.0.00 |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
class Barracuda_Sniffs_Commenting_DisallowSingleLineMultiCommentsSniff implements PHP_CodeSniffer_Sniff |
|||
{ |
|||
|
|||
|
|||
/** |
|||
* Returns the token types that this sniff is interested in. |
|||
* |
|||
* @return array(int) |
|||
*/ |
|||
public function register() |
|||
{ |
|||
return array(T_COMMENT); |
|||
|
|||
}// end register() |
|||
|
|||
|
|||
/** |
|||
* Processes the tokens that this sniff is interested in. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. |
|||
* @param int $stackPtr The position in the stack where |
|||
* the token was found. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
if (preg_match('/\/\*[^\n]*\*\//', $tokens[$stackPtr]['content'])) |
|||
{ |
|||
$error = 'Multi line comments are prohibited on single lines; found %s'; |
|||
$data = array(trim($tokens[$stackPtr]['content'])); |
|||
$phpcsFile->addError($error, $stackPtr, 'Found', $data); |
|||
} |
|||
|
|||
}// end process() |
|||
} |
|||
// end class |
|||
|
@ -0,0 +1,493 @@ |
|||
<?php |
|||
|
|||
class Barracuda_Sniffs_Commenting_DocCommentSniff implements PHP_CodeSniffer_Sniff |
|||
{ |
|||
|
|||
/** |
|||
* Tags in correct order and related info. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $tags = array( |
|||
'@category' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@package' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@subpackage' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@author' => array( |
|||
'required' => false, |
|||
'allow_multiple' => true, |
|||
), |
|||
'@copyright' => array( |
|||
'required' => false, |
|||
'allow_multiple' => true, |
|||
), |
|||
'@license' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@version' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@link' => array( |
|||
'required' => false, |
|||
'allow_multiple' => true, |
|||
), |
|||
'@see' => array( |
|||
'required' => false, |
|||
'allow_multiple' => true, |
|||
), |
|||
'@since' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
'@deprecated' => array( |
|||
'required' => false, |
|||
'allow_multiple' => false, |
|||
), |
|||
); |
|||
|
|||
|
|||
/** |
|||
* Returns an array of tokens this test wants to listen for. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function register() |
|||
{ |
|||
return array(T_OPEN_TAG); |
|||
|
|||
}//end register() |
|||
|
|||
|
|||
/** |
|||
* Processes this test, when one of its tokens is encountered. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* |
|||
* @return int |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
// Find the next non whitespace token. |
|||
$commentStart = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true); |
|||
|
|||
// Allow declare() statements at the top of the file. |
|||
if ($tokens[$commentStart]['code'] === T_DECLARE) { |
|||
$semicolon = $phpcsFile->findNext(T_SEMICOLON, ($commentStart + 1)); |
|||
$commentStart = $phpcsFile->findNext(T_WHITESPACE, ($semicolon + 1), null, true); |
|||
} |
|||
|
|||
// Ignore vim header. |
|||
if ($tokens[$commentStart]['code'] === T_COMMENT) { |
|||
if (strstr($tokens[$commentStart]['content'], 'vim:') !== false) { |
|||
$commentStart = $phpcsFile->findNext( |
|||
T_WHITESPACE, |
|||
($commentStart + 1), |
|||
null, |
|||
true |
|||
); |
|||
} |
|||
} |
|||
|
|||
$errorToken = ($stackPtr + 1); |
|||
if (isset($tokens[$errorToken]) === false) { |
|||
$errorToken--; |
|||
} |
|||
|
|||
if ($tokens[$commentStart]['code'] === T_CLOSE_TAG) { |
|||
// We are only interested if this is the first open tag. |
|||
return ($phpcsFile->numTokens + 1); |
|||
} else if ($tokens[$commentStart]['code'] === T_COMMENT) { |
|||
$error = 'You must use "/**" style comments for a file comment'; |
|||
$phpcsFile->addError($error, $errorToken, 'WrongStyle'); |
|||
$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); |
|||
return ($phpcsFile->numTokens + 1); |
|||
} else if ($commentStart === false |
|||
|| $tokens[$commentStart]['code'] !== T_DOC_COMMENT_OPEN_TAG |
|||
) { |
|||
$phpcsFile->addError('Missing file doc comment', $errorToken, 'Missing'); |
|||
$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'no'); |
|||
return ($phpcsFile->numTokens + 1); |
|||
} else { |
|||
$phpcsFile->recordMetric($stackPtr, 'File has doc comment', 'yes'); |
|||
} |
|||
|
|||
// Check the PHP Version, which should be in some text before the first tag. |
|||
$commentEnd = $tokens[$commentStart]['comment_closer']; |
|||
$found = false; |
|||
for ($i = ($commentStart + 1); $i < $commentEnd; $i++) { |
|||
if ($tokens[$i]['code'] === T_DOC_COMMENT_TAG) { |
|||
break; |
|||
} else if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING |
|||
&& strstr(strtolower($tokens[$i]['content']), 'php version') !== false |
|||
) { |
|||
$found = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if ($found === false) { |
|||
$error = 'PHP version not specified'; |
|||
$phpcsFile->addWarning($error, $commentEnd, 'MissingVersion'); |
|||
} |
|||
|
|||
// Check each tag. |
|||
$this->processTags($phpcsFile, $stackPtr, $commentStart); |
|||
|
|||
// Ignore the rest of the file. |
|||
return ($phpcsFile->numTokens + 1); |
|||
|
|||
}//end process() |
|||
|
|||
|
|||
/** |
|||
* Processes each required or optional tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param int $commentStart Position in the stack where the comment started. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processTags(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
if (get_class($this) === 'PEAR_Sniffs_Commenting_FileCommentSniff') { |
|||
$docBlock = 'file'; |
|||
} else { |
|||
$docBlock = 'class'; |
|||
} |
|||
|
|||
$commentEnd = $tokens[$commentStart]['comment_closer']; |
|||
|
|||
$foundTags = array(); |
|||
$tagTokens = array(); |
|||
foreach ($tokens[$commentStart]['comment_tags'] as $tag) { |
|||
$name = $tokens[$tag]['content']; |
|||
if (isset($this->tags[$name]) === false) { |
|||
continue; |
|||
} |
|||
|
|||
if ($this->tags[$name]['allow_multiple'] === false && isset($tagTokens[$name]) === true) { |
|||
$error = 'Only one %s tag is allowed in a %s comment'; |
|||
$data = array( |
|||
$name, |
|||
$docBlock, |
|||
); |
|||
$phpcsFile->addError($error, $tag, 'Duplicate'.ucfirst(substr($name, 1)).'Tag', $data); |
|||
} |
|||
|
|||
$foundTags[] = $name; |
|||
$tagTokens[$name][] = $tag; |
|||
|
|||
$string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); |
|||
if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { |
|||
$error = 'Content missing for %s tag in %s comment'; |
|||
$data = array( |
|||
$name, |
|||
$docBlock, |
|||
); |
|||
$phpcsFile->addError($error, $tag, 'Empty'.ucfirst(substr($name, 1)).'Tag', $data); |
|||
continue; |
|||
} |
|||
}//end foreach |
|||
|
|||
// Check if the tags are in the correct position. |
|||
$pos = 0; |
|||
foreach ($this->tags as $tag => $tagData) { |
|||
if (isset($tagTokens[$tag]) === false) { |
|||
if ($tagData['required'] === true) { |
|||
$error = 'Missing %s tag in %s comment'; |
|||
$data = array( |
|||
$tag, |
|||
$docBlock, |
|||
); |
|||
$phpcsFile->addError($error, $commentEnd, 'Missing'.ucfirst(substr($tag, 1)).'Tag', $data); |
|||
} |
|||
|
|||
continue; |
|||
} else { |
|||
$method = 'process'.substr($tag, 1); |
|||
if (method_exists($this, $method) === true) { |
|||
// Process each tag if a method is defined. |
|||
call_user_func(array($this, $method), $phpcsFile, $tagTokens[$tag]); |
|||
} |
|||
} |
|||
|
|||
if (isset($foundTags[$pos]) === false) { |
|||
break; |
|||
} |
|||
|
|||
if ($foundTags[$pos] !== $tag) { |
|||
$error = 'The tag in position %s should be the %s tag'; |
|||
$data = array( |
|||
($pos + 1), |
|||
$tag, |
|||
); |
|||
$phpcsFile->addError($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1)).'TagOrder', $data); |
|||
} |
|||
|
|||
// Account for multiple tags. |
|||
$pos++; |
|||
while (isset($foundTags[$pos]) === true && $foundTags[$pos] === $tag) { |
|||
$pos++; |
|||
} |
|||
}//end foreach |
|||
|
|||
}//end processTags() |
|||
|
|||
|
|||
/** |
|||
* Process the category tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param array $tags The tokens for these tags. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processCategory(PHP_CodeSniffer_File $phpcsFile, array $tags) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
foreach ($tags as $tag) { |
|||
if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
// No content. |
|||
continue; |
|||
} |
|||
|
|||
$content = $tokens[($tag + 2)]['content']; |
|||
if (PHP_CodeSniffer::isUnderscoreName($content) !== true) { |
|||
$newContent = str_replace(' ', '_', $content); |
|||
$nameBits = explode('_', $newContent); |
|||
$firstBit = array_shift($nameBits); |
|||
$newName = ucfirst($firstBit).'_'; |
|||
foreach ($nameBits as $bit) { |
|||
if ($bit !== '') { |
|||
$newName .= ucfirst($bit).'_'; |
|||
} |
|||
} |
|||
|
|||
$error = 'Category name "%s" is not valid; consider "%s" instead'; |
|||
$validName = trim($newName, '_'); |
|||
$data = array( |
|||
$content, |
|||
$validName, |
|||
); |
|||
$phpcsFile->addError($error, $tag, 'InvalidCategory', $data); |
|||
} |
|||
}//end foreach |
|||
|
|||
}//end processCategory() |
|||
|
|||
|
|||
/** |
|||
* Process the package tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param array $tags The tokens for these tags. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processPackage(PHP_CodeSniffer_File $phpcsFile, array $tags) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
foreach ($tags as $tag) { |
|||
if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
// No content. |
|||
continue; |
|||
} |
|||
|
|||
$content = $tokens[($tag + 2)]['content']; |
|||
if (PHP_CodeSniffer::isUnderscoreName($content) === true) { |
|||
continue; |
|||
} |
|||
|
|||
$newContent = str_replace(' ', '_', $content); |
|||
$newContent = trim($newContent, '_'); |
|||
$newContent = preg_replace('/[^A-Za-z_]/', '', $newContent); |
|||
$nameBits = explode('_', $newContent); |
|||
$firstBit = array_shift($nameBits); |
|||
$newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_'; |
|||
foreach ($nameBits as $bit) { |
|||
if ($bit !== '') { |
|||
$newName .= strtoupper($bit{0}).substr($bit, 1).'_'; |
|||
} |
|||
} |
|||
|
|||
$error = 'Package name "%s" is not valid; consider "%s" instead'; |
|||
$validName = trim($newName, '_'); |
|||
$data = array( |
|||
$content, |
|||
$validName, |
|||
); |
|||
$phpcsFile->addError($error, $tag, 'InvalidPackage', $data); |
|||
}//end foreach |
|||
|
|||
}//end processPackage() |
|||
|
|||
|
|||
/** |
|||
* Process the subpackage tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param array $tags The tokens for these tags. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processSubpackage(PHP_CodeSniffer_File $phpcsFile, array $tags) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
foreach ($tags as $tag) { |
|||
if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
// No content. |
|||
continue; |
|||
} |
|||
|
|||
$content = $tokens[($tag + 2)]['content']; |
|||
if (PHP_CodeSniffer::isUnderscoreName($content) === true) { |
|||
continue; |
|||
} |
|||
|
|||
$newContent = str_replace(' ', '_', $content); |
|||
$nameBits = explode('_', $newContent); |
|||
$firstBit = array_shift($nameBits); |
|||
$newName = strtoupper($firstBit{0}).substr($firstBit, 1).'_'; |
|||
foreach ($nameBits as $bit) { |
|||
if ($bit !== '') { |
|||
$newName .= strtoupper($bit{0}).substr($bit, 1).'_'; |
|||
} |
|||
} |
|||
|
|||
$error = 'Subpackage name "%s" is not valid; consider "%s" instead'; |
|||
$validName = trim($newName, '_'); |
|||
$data = array( |
|||
$content, |
|||
$validName, |
|||
); |
|||
$phpcsFile->addError($error, $tag, 'InvalidSubpackage', $data); |
|||
}//end foreach |
|||
|
|||
}//end processSubpackage() |
|||
|
|||
/** |
|||
* Author tag must be 'Firstname Lastname <email@barracuda.com>'. |
|||
* |
|||
* @param int $errorPos The line number where the error occurs. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processAuthors($errorPos) |
|||
{ |
|||
$authors = $this->commentParser->getAuthors(); |
|||
if (empty($authors) === false) { |
|||
$author = $authors[0]; |
|||
$content = $author->getContent(); |
|||
if (empty($content) === true) { |
|||
$error = 'Content missing for @author tag in file comment'; |
|||
$this->currentFile->addError($error, $errorPos, 'MissingAuthor'); |
|||
} else if (preg_match('/^(.*) \<.*\@.*\>$/', $content) === 0) { |
|||
$error = 'Expected "Firstname Lastname <email@domain.com>" for author tag'; |
|||
$this->currentFile->addError($error, $errorPos, 'IncorrectAuthor'); |
|||
} |
|||
} |
|||
}//end processAuthors() |
|||
|
|||
/** |
|||
* Copyright tag must be in the form 'xxxx-xxxx Barracuda Networks, Inc.'. |
|||
* |
|||
* @param int $errorPos The line number where the error occurs. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processCopyrights($errorPos) |
|||
{ |
|||
$copyrights = $this->commentParser->getCopyrights(); |
|||
$copyright = $copyrights[0]; |
|||
if ($copyright !== null) { |
|||
$content = $copyright->getContent(); |
|||
if (empty($content) === true) { |
|||
$error = 'Content missing for @copyright tag in file comment'; |
|||
$this->currentFile->addError($error, $errorPos, 'MissingCopyright'); |
|||
} else if (preg_match('/^([0-9]{4})(-[0-9]{4})? (\.*\)$/', $content) === 0) { |
|||
$error = 'Expected "xxxx-xxxx Barracuda Networks, Inc." for copyright declaration'; |
|||
$this->currentFile->addError($error, $errorPos, 'IncorrectCopyright'); |
|||
} |
|||
} |
|||
}//end processCopyrights() |
|||
|
|||
/** |
|||
* Process the license tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param array $tags The tokens for these tags. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processLicense(PHP_CodeSniffer_File $phpcsFile, array $tags) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
foreach ($tags as $tag) { |
|||
if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
// No content. |
|||
continue; |
|||
} |
|||
|
|||
$content = $tokens[($tag + 2)]['content']; |
|||
$matches = array(); |
|||
preg_match('/^([^\s]+)\s+(.*)/', $content, $matches); |
|||
if (count($matches) !== 3) { |
|||
$error = '@license tag must contain a URL and a license name'; |
|||
$phpcsFile->addError($error, $tag, 'IncompleteLicense'); |
|||
} |
|||
} |
|||
|
|||
}//end processLicense() |
|||
|
|||
|
|||
/** |
|||
* Process the version tag. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param array $tags The tokens for these tags. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processVersion(PHP_CodeSniffer_File $phpcsFile, array $tags) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
foreach ($tags as $tag) { |
|||
if ($tokens[($tag + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
// No content. |
|||
continue; |
|||
} |
|||
|
|||
$content = $tokens[($tag + 2)]['content']; |
|||
if (strstr($content, 'CVS:') === false |
|||
&& strstr($content, 'SVN:') === false |
|||
&& strstr($content, 'GIT:') === false |
|||
&& strstr($content, 'HG:') === false |
|||
) { |
|||
$error = 'Invalid version "%s" in file comment; consider "CVS: <cvs_id>" or "SVN: <svn_id>" or "GIT: <git_id>" or "HG: <hg_id>" instead'; |
|||
$data = array($content); |
|||
$phpcsFile->addWarning($error, $tag, 'InvalidVersion', $data); |
|||
} |
|||
} |
|||
|
|||
}//end processVersion() |
|||
|
|||
|
|||
}//end class |
@ -0,0 +1,556 @@ |
|||
<?php |
|||
/** |
|||
* Parses and verifies the doc comments for functions. |
|||
* |
|||
* PHP version 5 |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @author Marc McIntyre <mmcintyre@squiz.net> |
|||
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
|
|||
if (class_exists('PEAR_Sniffs_Commenting_FunctionCommentSniff', true) === false) { |
|||
throw new PHP_CodeSniffer_Exception('Class PEAR_Sniffs_Commenting_FunctionCommentSniff not found'); |
|||
} |
|||
|
|||
/** |
|||
* Parses and verifies the doc comments for functions. |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @author Marc McIntyre <mmcintyre@squiz.net> |
|||
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @version Release: @package_version@ |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
class Barracuda_Sniffs_Commenting_FunctionCommentSniff extends PEAR_Sniffs_Commenting_FunctionCommentSniff |
|||
{ |
|||
protected $allowedTypes = array( |
|||
'int', |
|||
'bool', |
|||
); |
|||
|
|||
public function __construct() |
|||
{ |
|||
PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes); |
|||
} |
|||
|
|||
/** |
|||
* Process the return comment of this function comment. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param int $commentStart The position in the stack where the comment started. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processReturn(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
// Skip constructor and destructor. |
|||
$methodName = $phpcsFile->getDeclarationName($stackPtr); |
|||
$isSpecialMethod = ($methodName === '__construct' || $methodName === '__destruct'); |
|||
|
|||
$return = null; |
|||
foreach ($tokens[$commentStart]['comment_tags'] as $tag) { |
|||
if ($tokens[$tag]['content'] === '@return') { |
|||
if ($return !== null) { |
|||
$error = 'Only 1 @return tag is allowed in a function comment'; |
|||
$phpcsFile->addError($error, $tag, 'DuplicateReturn'); |
|||
return; |
|||
} |
|||
|
|||
$return = $tag; |
|||
} |
|||
} |
|||
|
|||
if ($isSpecialMethod === true) { |
|||
return; |
|||
} |
|||
|
|||
if ($return !== null) { |
|||
$content = $tokens[($return + 2)]['content']; |
|||
if (empty($content) === true || $tokens[($return + 2)]['code'] !== T_DOC_COMMENT_STRING) { |
|||
$error = 'Return type missing for @return tag in function comment'; |
|||
$phpcsFile->addError($error, $return, 'MissingReturnType'); |
|||
} else { |
|||
// Check return type (can be multiple, separated by '|'). |
|||
$typeNames = explode('|', $content); |
|||
$suggestedNames = array(); |
|||
foreach ($typeNames as $i => $typeName) { |
|||
$suggestedName = PHP_CodeSniffer::suggestType($typeName); |
|||
if (in_array($suggestedName, $suggestedNames) === false) { |
|||
$suggestedNames[] = $suggestedName; |
|||
} |
|||
} |
|||
|
|||
$suggestedType = implode('|', $suggestedNames); |
|||
if ($content !== $suggestedType) { |
|||
$error = 'Expected "%s" but found "%s" for function return type'; |
|||
$data = array( |
|||
$suggestedType, |
|||
$content, |
|||
); |
|||
$fix = $phpcsFile->addFixableError($error, $return, 'InvalidReturn', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->replaceToken(($return + 2), $suggestedType); |
|||
} |
|||
} |
|||
|
|||
// If the return type is void, make sure there is |
|||
// no return statement in the function. |
|||
if ($content === 'void') { |
|||
if (isset($tokens[$stackPtr]['scope_closer']) === true) { |
|||
$endToken = $tokens[$stackPtr]['scope_closer']; |
|||
for ($returnToken = $stackPtr; $returnToken < $endToken; $returnToken++) { |
|||
if ($tokens[$returnToken]['code'] === T_CLOSURE) { |
|||
$returnToken = $tokens[$returnToken]['scope_closer']; |
|||
continue; |
|||
} |
|||
|
|||
if ($tokens[$returnToken]['code'] === T_RETURN |
|||
|| $tokens[$returnToken]['code'] === T_YIELD |
|||
) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if ($returnToken !== $endToken) { |
|||
// If the function is not returning anything, just |
|||
// exiting, then there is no problem. |
|||
$semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); |
|||
if ($tokens[$semicolon]['code'] !== T_SEMICOLON) { |
|||
$error = 'Function return type is void, but function contains return statement'; |
|||
$phpcsFile->addError($error, $return, 'InvalidReturnVoid'); |
|||
} |
|||
} |
|||
}//end if |
|||
} else if ($content !== 'mixed') { |
|||
// If return type is not void, there needs to be a return statement |
|||
// somewhere in the function that returns something. |
|||
if (isset($tokens[$stackPtr]['scope_closer']) === true) { |
|||
$endToken = $tokens[$stackPtr]['scope_closer']; |
|||
$returnToken = $phpcsFile->findNext(array(T_RETURN, T_YIELD), $stackPtr, $endToken); |
|||
if ($returnToken === false) { |
|||
$error = 'Function return type is not void, but function has no return statement'; |
|||
$phpcsFile->addError($error, $return, 'InvalidNoReturn'); |
|||
} else { |
|||
$semicolon = $phpcsFile->findNext(T_WHITESPACE, ($returnToken + 1), null, true); |
|||
if ($tokens[$semicolon]['code'] === T_SEMICOLON) { |
|||
$error = 'Function return type is not void, but function is returning void here'; |
|||
$phpcsFile->addError($error, $returnToken, 'InvalidReturnNotVoid'); |
|||
} |
|||
} |
|||
} |
|||
}//end if |
|||
}//end if |
|||
} else { |
|||
$error = 'Missing @return tag in function comment'; |
|||
$phpcsFile->addError($error, $tokens[$commentStart]['comment_closer'], 'MissingReturn'); |
|||
}//end if |
|||
|
|||
}//end processReturn() |
|||
|
|||
|
|||
/** |
|||
* Process any throw tags that this function comment has. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param int $commentStart The position in the stack where the comment started. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processThrows(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
$throws = array(); |
|||
foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { |
|||
if ($tokens[$tag]['content'] !== '@throws') { |
|||
continue; |
|||
} |
|||
|
|||
$exception = null; |
|||
$comment = null; |
|||
if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) { |
|||
$matches = array(); |
|||
preg_match('/([^\s]+)(?:\s+(.*))?/', $tokens[($tag + 2)]['content'], $matches); |
|||
$exception = $matches[1]; |
|||
if (isset($matches[2]) === true && trim($matches[2]) !== '') { |
|||
$comment = $matches[2]; |
|||
} |
|||
} |
|||
|
|||
if ($exception === null) { |
|||
$error = 'Exception type and comment missing for @throws tag in function comment'; |
|||
$phpcsFile->addError($error, $tag, 'InvalidThrows'); |
|||
} else if ($comment === null) { |
|||
$error = 'Comment missing for @throws tag in function comment'; |
|||
$phpcsFile->addError($error, $tag, 'EmptyThrows'); |
|||
} else { |
|||
// Any strings until the next tag belong to this comment. |
|||
if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) { |
|||
$end = $tokens[$commentStart]['comment_tags'][($pos + 1)]; |
|||
} else { |
|||
$end = $tokens[$commentStart]['comment_closer']; |
|||
} |
|||
|
|||
for ($i = ($tag + 3); $i < $end; $i++) { |
|||
if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { |
|||
$comment .= ' '.$tokens[$i]['content']; |
|||
} |
|||
} |
|||
|
|||
// Starts with a capital letter and ends with a fullstop. |
|||
$firstChar = $comment{0}; |
|||
if (strtoupper($firstChar) !== $firstChar) { |
|||
$error = '@throws tag comment must start with a capital letter'; |
|||
$phpcsFile->addError($error, ($tag + 2), 'ThrowsNotCapital'); |
|||
} |
|||
|
|||
$lastChar = substr($comment, -1); |
|||
if ($lastChar !== '.') { |
|||
$error = '@throws tag comment must end with a full stop'; |
|||
$phpcsFile->addError($error, ($tag + 2), 'ThrowsNoFullStop'); |
|||
} |
|||
}//end if |
|||
}//end foreach |
|||
|
|||
}//end processThrows() |
|||
|
|||
|
|||
/** |
|||
* Process the function parameter comments. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param int $commentStart The position in the stack where the comment started. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processParams(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $commentStart) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
$params = array(); |
|||
$maxType = 0; |
|||
$maxVar = 0; |
|||
foreach ($tokens[$commentStart]['comment_tags'] as $pos => $tag) { |
|||
if ($tokens[$tag]['content'] !== '@param') { |
|||
continue; |
|||
} |
|||
|
|||
$type = ''; |
|||
$typeSpace = 0; |
|||
$var = ''; |
|||
$varSpace = 0; |
|||
$comment = ''; |
|||
$commentLines = array(); |
|||
if ($tokens[($tag + 2)]['code'] === T_DOC_COMMENT_STRING) { |
|||
$matches = array(); |
|||
preg_match('/([^$&]+)(?:((?:\$|&)[^\s]+)(?:(\s+)(.*))?)?/', $tokens[($tag + 2)]['content'], $matches); |
|||
|
|||
$typeLen = strlen($matches[1]); |
|||
$type = trim($matches[1]); |
|||
$typeSpace = ($typeLen - strlen($type)); |
|||
$typeLen = strlen($type); |
|||
if ($typeLen > $maxType) { |
|||
$maxType = $typeLen; |
|||
} |
|||
|
|||
if (isset($matches[2]) === true) { |
|||
$var = $matches[2]; |
|||
$varLen = strlen($var); |
|||
if ($varLen > $maxVar) { |
|||
$maxVar = $varLen; |
|||
} |
|||
|
|||
if (isset($matches[4]) === true) { |
|||
$varSpace = strlen($matches[3]); |
|||
$comment = $matches[4]; |
|||
$commentLines[] = array( |
|||
'comment' => $comment, |
|||
'token' => ($tag + 2), |
|||
'indent' => $varSpace, |
|||
); |
|||
|
|||
// Any strings until the next tag belong to this comment. |
|||
if (isset($tokens[$commentStart]['comment_tags'][($pos + 1)]) === true) { |
|||
$end = $tokens[$commentStart]['comment_tags'][($pos + 1)]; |
|||
} else { |
|||
$end = $tokens[$commentStart]['comment_closer']; |
|||
} |
|||
|
|||
for ($i = ($tag + 3); $i < $end; $i++) { |
|||
if ($tokens[$i]['code'] === T_DOC_COMMENT_STRING) { |
|||
$indent = 0; |
|||
if ($tokens[($i - 1)]['code'] === T_DOC_COMMENT_WHITESPACE) { |
|||
$indent = strlen($tokens[($i - 1)]['content']); |
|||
} |
|||
|
|||
$comment .= ' '.$tokens[$i]['content']; |
|||
$commentLines[] = array( |
|||
'comment' => $tokens[$i]['content'], |
|||
'token' => $i, |
|||
'indent' => $indent, |
|||
); |
|||
} |
|||
} |
|||
} else { |
|||
$error = 'Missing parameter comment'; |
|||
$phpcsFile->addError($error, $tag, 'MissingParamComment'); |
|||
$commentLines[] = array('comment' => ''); |
|||
}//end if |
|||
} else { |
|||
$error = 'Missing parameter name'; |
|||
$phpcsFile->addError($error, $tag, 'MissingParamName'); |
|||
}//end if |
|||
} else { |
|||
$error = 'Missing parameter type'; |
|||
$phpcsFile->addError($error, $tag, 'MissingParamType'); |
|||
}//end if |
|||
|
|||
$params[] = array( |
|||
'tag' => $tag, |
|||
'type' => $type, |
|||
'var' => $var, |
|||
'comment' => $comment, |
|||
'commentLines' => $commentLines, |
|||
'type_space' => $typeSpace, |
|||
'var_space' => $varSpace, |
|||
); |
|||
}//end foreach |
|||
|
|||
$realParams = $phpcsFile->getMethodParameters($stackPtr); |
|||
$foundParams = array(); |
|||
|
|||
foreach ($params as $pos => $param) { |
|||
// If the type is empty, the whole line is empty. |
|||
if ($param['type'] === '') { |
|||
continue; |
|||
} |
|||
|
|||
// Check the param type value. |
|||
$typeNames = explode('|', $param['type']); |
|||
foreach ($typeNames as $typeName) { |
|||
$suggestedName = PHP_CodeSniffer::suggestType($typeName); |
|||
if ($typeName !== $suggestedName) { |
|||
$error = 'Expected "%s" but found "%s" for parameter type'; |
|||
$data = array( |
|||
$suggestedName, |
|||
$typeName, |
|||
); |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, $param['tag'], 'IncorrectParamVarName', $data); |
|||
if ($fix === true) { |
|||
$content = $suggestedName; |
|||
$content .= str_repeat(' ', $param['type_space']); |
|||
$content .= $param['var']; |
|||
$content .= str_repeat(' ', $param['var_space']); |
|||
if (isset($param['commentLines'][0]) === true) { |
|||
$content .= $param['commentLines'][0]['comment']; |
|||
} |
|||
|
|||
$phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content); |
|||
} |
|||
} else if (count($typeNames) === 1) { |
|||
// Check type hint for array and custom type. |
|||
$suggestedTypeHint = ''; |
|||
if (strpos($suggestedName, 'array') !== false || substr($suggestedName, -2) === '[]') { |
|||
$suggestedTypeHint = 'array'; |
|||
} else if (strpos($suggestedName, 'callable') !== false) { |
|||
$suggestedTypeHint = 'callable'; |
|||
} else if (strpos($suggestedName, 'callback') !== false) { |
|||
$suggestedTypeHint = 'callable'; |
|||
} else if (in_array($typeName, PHP_CodeSniffer::$allowedTypes) === false) { |
|||
$suggestedTypeHint = $suggestedName; |
|||
} |
|||
|
|||
if ($suggestedTypeHint !== '' && isset($realParams[$pos]) === true) { |
|||
$typeHint = $realParams[$pos]['type_hint']; |
|||
if ($typeHint === '') { |
|||
$error = 'Type hint "%s" missing for %s'; |
|||
$data = array( |
|||
$suggestedTypeHint, |
|||
$param['var'], |
|||
); |
|||
$phpcsFile->addError($error, $stackPtr, 'TypeHintMissing', $data); |
|||
} else if ($typeHint !== substr($suggestedTypeHint, (strlen($typeHint) * -1))) { |
|||
$error = 'Expected type hint "%s"; found "%s" for %s'; |
|||
$data = array( |
|||
$suggestedTypeHint, |
|||
$typeHint, |
|||
$param['var'], |
|||
); |
|||
$phpcsFile->addError($error, $stackPtr, 'IncorrectTypeHint', $data); |
|||
} |
|||
} else if ($suggestedTypeHint === '' && isset($realParams[$pos]) === true) { |
|||
$typeHint = $realParams[$pos]['type_hint']; |
|||
if ($typeHint !== '') { |
|||
$error = 'Unknown type hint "%s" found for %s'; |
|||
$data = array( |
|||
$typeHint, |
|||
$param['var'], |
|||
); |
|||
$phpcsFile->addError($error, $stackPtr, 'InvalidTypeHint', $data); |
|||
} |
|||
}//end if |
|||
}//end if |
|||
}//end foreach |
|||
|
|||
if ($param['var'] === '') { |
|||
continue; |
|||
} |
|||
|
|||
$foundParams[] = $param['var']; |
|||
|
|||
// Check number of spaces after the type. |
|||
$spaces = ($maxType - strlen($param['type']) + 1); |
|||
if ($param['type_space'] !== $spaces) { |
|||
$error = 'Expected %s spaces after parameter type; %s found'; |
|||
$data = array( |
|||
$spaces, |
|||
$param['type_space'], |
|||
); |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamType', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
|
|||
$content = $param['type']; |
|||
$content .= str_repeat(' ', $spaces); |
|||
$content .= $param['var']; |
|||
$content .= str_repeat(' ', $param['var_space']); |
|||
$content .= $param['commentLines'][0]['comment']; |
|||
$phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content); |
|||
|
|||
// Fix up the indent of additional comment lines. |
|||
foreach ($param['commentLines'] as $lineNum => $line) { |
|||
if ($lineNum === 0 |
|||
|| $param['commentLines'][$lineNum]['indent'] === 0 |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
$newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['type_space']); |
|||
$phpcsFile->fixer->replaceToken( |
|||
($param['commentLines'][$lineNum]['token'] - 1), |
|||
str_repeat(' ', $newIndent) |
|||
); |
|||
} |
|||
|
|||
$phpcsFile->fixer->endChangeset(); |
|||
}//end if |
|||
}//end if |
|||
|
|||
// Make sure the param name is correct. |
|||
if (isset($realParams[$pos]) === true) { |
|||
$realName = $realParams[$pos]['name']; |
|||
if ($realName !== $param['var']) { |
|||
$code = 'ParamNameNoMatch'; |
|||
$data = array( |
|||
$param['var'], |
|||
$realName, |
|||
); |
|||
|
|||
$error = 'Doc comment for parameter %s does not match '; |
|||
if (strtolower($param['var']) === strtolower($realName)) { |
|||
$error .= 'case of '; |
|||
$code = 'ParamNameNoCaseMatch'; |
|||
} |
|||
|
|||
$error .= 'actual variable name %s'; |
|||
|
|||
$phpcsFile->addError($error, $param['tag'], $code, $data); |
|||
} |
|||
} else if (substr($param['var'], -4) !== ',...') { |
|||
// We must have an extra parameter comment. |
|||
$error = 'Superfluous parameter comment'; |
|||
$phpcsFile->addError($error, $param['tag'], 'ExtraParamComment'); |
|||
}//end if |
|||
|
|||
if ($param['comment'] === '') { |
|||
continue; |
|||
} |
|||
|
|||
// Check number of spaces after the var name. |
|||
$spaces = ($maxVar - strlen($param['var']) + 1); |
|||
if ($param['var_space'] !== $spaces) { |
|||
$error = 'Expected %s spaces after parameter name; %s found'; |
|||
$data = array( |
|||
$spaces, |
|||
$param['var_space'], |
|||
); |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, $param['tag'], 'SpacingAfterParamName', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
|
|||
$content = $param['type']; |
|||
$content .= str_repeat(' ', $param['type_space']); |
|||
$content .= $param['var']; |
|||
$content .= str_repeat(' ', $spaces); |
|||
$content .= $param['commentLines'][0]['comment']; |
|||
$phpcsFile->fixer->replaceToken(($param['tag'] + 2), $content); |
|||
|
|||
// Fix up the indent of additional comment lines. |
|||
foreach ($param['commentLines'] as $lineNum => $line) { |
|||
if ($lineNum === 0 |
|||
|| $param['commentLines'][$lineNum]['indent'] === 0 |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
$newIndent = ($param['commentLines'][$lineNum]['indent'] + $spaces - $param['var_space']); |
|||
$phpcsFile->fixer->replaceToken( |
|||
($param['commentLines'][$lineNum]['token'] - 1), |
|||
str_repeat(' ', $newIndent) |
|||
); |
|||
} |
|||
|
|||
$phpcsFile->fixer->endChangeset(); |
|||
}//end if |
|||
}//end if |
|||
|
|||
// Param comments must start with a capital letter and end with the full stop. |
|||
$firstChar = $param['comment']{0}; |
|||
if (preg_match('|\p{Lu}|u', $firstChar) === 0) { |
|||
$error = 'Parameter comment must start with a capital letter'; |
|||
$phpcsFile->addError($error, $param['tag'], 'ParamCommentNotCapital'); |
|||
} |
|||
|
|||
$lastChar = substr($param['comment'], -1); |
|||
if ($lastChar !== '.') { |
|||
$error = 'Parameter comment must end with a full stop'; |
|||
$phpcsFile->addError($error, $param['tag'], 'ParamCommentFullStop'); |
|||
} |
|||
}//end foreach |
|||
|
|||
$realNames = array(); |
|||
foreach ($realParams as $realParam) { |
|||
$realNames[] = $realParam['name']; |
|||
} |
|||
|
|||
// Report missing comments. |
|||
$diff = array_diff($realNames, $foundParams); |
|||
foreach ($diff as $neededParam) { |
|||
$error = 'Doc comment for parameter "%s" missing'; |
|||
$data = array($neededParam); |
|||
$phpcsFile->addError($error, $commentStart, 'MissingParamTag', $data); |
|||
} |
|||
|
|||
}//end processParams() |
|||
|
|||
|
|||
}//end class |
@ -0,0 +1,51 @@ |
|||
<?php |
|||
class Barracuda_Sniffs_Commenting_SpaceAfterCommentSniff implements PHP_CodeSniffer_Sniff |
|||
{ |
|||
/** |
|||
* Returns the token types that this sniff is interested in. |
|||
* |
|||
* @return array(int) |
|||
*/ |
|||
public function register() |
|||
{ |
|||
return array(T_COMMENT); |
|||
|
|||
} // end register() |
|||
|
|||
|
|||
/** |
|||
* Processes the tokens that this sniff is interested in. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. |
|||
* @param int $stackPtr The position in the stack where |
|||
* the token was found. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
$valid = false; |
|||
|
|||
if (preg_match('|//\s|', $tokens[$stackPtr]['content'])) |
|||
{ |
|||
$valid = true; |
|||
} |
|||
|
|||
if (preg_match('|\*[\s/]|', $tokens[$stackPtr]['content'])) |
|||
{ |
|||
$valid = true; |
|||
} |
|||
|
|||
if ($valid === false) |
|||
{ |
|||
$error = 'A space is required at the start of the comment %s'; |
|||
$data = array(trim($tokens[$stackPtr]['content'])); |
|||
$phpcsFile->addError($error, $stackPtr, 'Found', $data); |
|||
} |
|||
|
|||
}// end process() |
|||
} |
|||
// end class |
|||
|
@ -0,0 +1,177 @@ |
|||
<?php |
|||
/** |
|||
* Parses and verifies the variable doc comment. |
|||
* |
|||
* PHP version 5 |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @author Marc McIntyre <mmcintyre@squiz.net> |
|||
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
|
|||
if (class_exists('PHP_CodeSniffer_Standards_AbstractVariableSniff', true) === false) { |
|||
throw new PHP_CodeSniffer_Exception('Class PHP_CodeSniffer_Standards_AbstractVariableSniff not found'); |
|||
} |
|||
|
|||
/** |
|||
* Parses and verifies the variable doc comment. |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @author Marc McIntyre <mmcintyre@squiz.net> |
|||
* @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @version Release: @package_version@ |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
|
|||
class Barracuda_Sniffs_Commenting_VariableCommentSniff extends PHP_CodeSniffer_Standards_AbstractVariableSniff |
|||
{ |
|||
protected $allowedTypes = array( |
|||
'bool', |
|||
'int', |
|||
); |
|||
|
|||
public function __construct() |
|||
{ |
|||
PHP_CodeSniffer::$allowedTypes = array_merge(PHP_CodeSniffer::$allowedTypes, $this->allowedTypes); |
|||
} |
|||
|
|||
/** |
|||
* Called to process class member vars. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function processMemberVar(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
$commentToken = array( |
|||
T_COMMENT, |
|||
T_DOC_COMMENT_CLOSE_TAG, |
|||
); |
|||
|
|||
$commentEnd = $phpcsFile->findPrevious($commentToken, $stackPtr); |
|||
if ($commentEnd === false) { |
|||
$phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing'); |
|||
return; |
|||
} |
|||
|
|||
if ($tokens[$commentEnd]['code'] === T_COMMENT) { |
|||
$phpcsFile->addError('You must use "/**" style comments for a member variable comment', $stackPtr, 'WrongStyle'); |
|||
return; |
|||
} else if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) { |
|||
$phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing'); |
|||
return; |
|||
} else { |
|||
// Make sure the comment we have found belongs to us. |
|||
$commentFor = $phpcsFile->findNext(array(T_VARIABLE, T_CLASS, T_INTERFACE), ($commentEnd + 1)); |
|||
if ($commentFor !== $stackPtr) { |
|||
$phpcsFile->addError('Missing member variable doc comment', $stackPtr, 'Missing'); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
$commentStart = $tokens[$commentEnd]['comment_opener']; |
|||
|
|||
$foundVar = null; |
|||
foreach ($tokens[$commentStart]['comment_tags'] as $tag) { |
|||
if ($tokens[$tag]['content'] === '@var') { |
|||
if ($foundVar !== null) { |
|||
$error = 'Only one @var tag is allowed in a member variable comment'; |
|||
$phpcsFile->addError($error, $tag, 'DuplicateVar'); |
|||
} else { |
|||
$foundVar = $tag; |
|||
} |
|||
} else if ($tokens[$tag]['content'] === '@see') { |
|||
// Make sure the tag isn't empty. |
|||
$string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $tag, $commentEnd); |
|||
if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) { |
|||
$error = 'Content missing for @see tag in member variable comment'; |
|||
$phpcsFile->addError($error, $tag, 'EmptySees'); |
|||
} |
|||
} else { |
|||
$error = '%s tag is not allowed in member variable comment'; |
|||
$data = array($tokens[$tag]['content']); |
|||
$phpcsFile->addWarning($error, $tag, 'TagNotAllowed', $data); |
|||
}//end if |
|||
}//end foreach |
|||
|
|||
// The @var tag is the only one we require. |
|||
if ($foundVar === null) { |
|||
$error = 'Missing @var tag in member variable comment'; |
|||
$phpcsFile->addError($error, $commentEnd, 'MissingVar'); |
|||
return; |
|||
} |
|||
|
|||
$firstTag = $tokens[$commentStart]['comment_tags'][0]; |
|||
if ($foundVar !== null && $tokens[$firstTag]['content'] !== '@var') { |
|||
$error = 'The @var tag must be the first tag in a member variable comment'; |
|||
$phpcsFile->addError($error, $foundVar, 'VarOrder'); |
|||
} |
|||
|
|||
// Make sure the tag isn't empty and has the correct padding. |
|||
$string = $phpcsFile->findNext(T_DOC_COMMENT_STRING, $foundVar, $commentEnd); |
|||
if ($string === false || $tokens[$string]['line'] !== $tokens[$foundVar]['line']) { |
|||
$error = 'Content missing for @var tag in member variable comment'; |
|||
$phpcsFile->addError($error, $foundVar, 'EmptyVar'); |
|||
return; |
|||
} |
|||
|
|||
$varType = $tokens[($foundVar + 2)]['content']; |
|||
$suggestedType = PHP_CodeSniffer::suggestType($varType); |
|||
if ($varType !== $suggestedType) { |
|||
$error = 'Expected "%s" but found "%s" for @var tag in member variable comment'; |
|||
$data = array( |
|||
$suggestedType, |
|||
$varType, |
|||
); |
|||
$phpcsFile->addError($error, ($foundVar + 2), 'IncorrectVarType', $data); |
|||
} |
|||
|
|||
}//end processMemberVar() |
|||
|
|||
|
|||
/** |
|||
* Called to process a normal variable. |
|||
* |
|||
* Not required for this sniff. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found. |
|||
* @param int $stackPtr The position where the double quoted |
|||
* string was found. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processVariable(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
|
|||
}//end processVariable() |
|||
|
|||
|
|||
/** |
|||
* Called to process variables found in double quoted strings. |
|||
* |
|||
* Not required for this sniff. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where this token was found. |
|||
* @param int $stackPtr The position where the double quoted |
|||
* string was found. |
|||
* |
|||
* @return void |
|||
*/ |
|||
protected function processVariableInString(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
|
|||
}//end processVariableInString() |
|||
|
|||
|
|||
}//end class |
@ -0,0 +1,275 @@ |
|||
<?php |
|||
|
|||
if (class_exists('Squiz_Sniffs_ControlStructures_ControlSignatureSniff', true) === false) |
|||
{ |
|||
throw new PHP_CodeSniffer_Exception('Class Squiz_Sniffs_ControlStructures_ControlSignatureSniff not found'); |
|||
} |
|||
|
|||
// subclasses squiz controlstructures sniff to allow dropped braces and prevent same-line ones |
|||
class Barracuda_Sniffs_ControlStructures_ControlSignatureSniff extends Squiz_Sniffs_ControlStructures_ControlSignatureSniff |
|||
{ |
|||
/** |
|||
* A list of tokenizers this sniff supports. |
|||
* |
|||
* @var array |
|||
*/ |
|||
public $supportedTokenizers = array('PHP'); |
|||
|
|||
/** |
|||
* Processes this test, when one of its tokens is encountered. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token in the |
|||
* stack passed in $tokens. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
if (isset($tokens[($stackPtr + 1)]) === false) { |
|||
return; |
|||
} |
|||
|
|||
// Single space after the keyword. |
|||
if (in_array($tokens[$stackPtr]['code'], array(T_CATCH, T_IF, T_WHILE, T_FOR, T_FOREACH, T_ELSEIF, T_SWITCH))) { |
|||
$found = 1; |
|||
if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { |
|||
$found = 0; |
|||
} else if ($tokens[($stackPtr + 1)]['content'] !== ' ') { |
|||
if (strpos($tokens[($stackPtr + 1)]['content'], $phpcsFile->eolChar) !== false) { |
|||
$found = 'newline'; |
|||
} else { |
|||
$found = strlen($tokens[($stackPtr + 1)]['content']); |
|||
} |
|||
} |
|||
|
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 space after %s keyword; %s found'; |
|||
$data = array( |
|||
strtoupper($tokens[$stackPtr]['content']), |
|||
$found, |
|||
); |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpaceAfterKeyword', $data); |
|||
if ($fix === true) { |
|||
if ($found === 0) { |
|||
$phpcsFile->fixer->addContent($stackPtr, ' '); |
|||
} else { |
|||
$phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Single newline after the keyword. |
|||
if (in_array($tokens[$stackPtr]['code'], array(T_TRY, T_DO, T_ELSE)) |
|||
&& isset($tokens[$stackPtr]['scope_opener']) === true |
|||
) { |
|||
$opener = $tokens[$stackPtr]['scope_opener']; |
|||
$found = ($tokens[$opener]['line'] - $tokens[$stackPtr]['line']); |
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 newline after % keyword; %s found'; |
|||
$data = array( |
|||
strtoupper($tokens[$stackPtr]['content']), |
|||
$found, |
|||
); |
|||
$fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterKeyword', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
for ($i = ($stackPtr + 1); $i < $opener; $i++) { |
|||
if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) { |
|||
break; |
|||
} |
|||
|
|||
$phpcsFile->fixer->replaceToken($i, ''); |
|||
} |
|||
|
|||
$phpcsFile->fixer->addContent($stackPtr, $phpcsFile->eolChar); |
|||
$phpcsFile->fixer->endChangeset(); |
|||
} |
|||
}//end if |
|||
}//end if |
|||
|
|||
// Single newline after closing parenthesis. |
|||
if (isset($tokens[$stackPtr]['parenthesis_closer']) === true |
|||
&& isset($tokens[$stackPtr]['scope_opener']) === true |
|||
) { |
|||
$closer = $tokens[$stackPtr]['parenthesis_closer']; |
|||
$opener = $tokens[$stackPtr]['scope_opener']; |
|||
$found = ($tokens[$opener]['line'] - $tokens[$closer]['line']); |
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 newline after closing parenthesis; %s found'; |
|||
$fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterCloseParenthesis', array($found)); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
for ($i = ($closer + 1); $i < $opener; $i++) { |
|||
if ($found > 0 && $tokens[$i]['line'] === $tokens[$opener]['line']) { |
|||
break; |
|||
} |
|||
|
|||
$phpcsFile->fixer->replaceToken($i, ''); |
|||
} |
|||
|
|||
$phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar); |
|||
$phpcsFile->fixer->endChangeset(); |
|||
} |
|||
}//end if |
|||
}//end if |
|||
|
|||
// Single newline after opening brace. |
|||
if (isset($tokens[$stackPtr]['scope_opener']) === true) { |
|||
$opener = $tokens[$stackPtr]['scope_opener']; |
|||
for ($next = ($opener + 1); $next < $phpcsFile->numTokens; $next++) { |
|||
$code = $tokens[$next]['code']; |
|||
|
|||
if ($code === T_WHITESPACE) { |
|||
continue; |
|||
} |
|||
|
|||
// Skip all empty tokens on the same line as the opener. |
|||
if ($tokens[$next]['line'] === $tokens[$opener]['line'] |
|||
&& (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true |
|||
|| $code === T_CLOSE_TAG) |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
// We found the first bit of a code, or a comment on the |
|||
// following line. |
|||
break; |
|||
} |
|||
|
|||
$found = ($tokens[$next]['line'] - $tokens[$opener]['line']); |
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 newline after opening brace; %s found'; |
|||
$data = array($found); |
|||
$fix = $phpcsFile->addFixableError($error, $opener, 'NewlineAfterOpenBrace', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
for ($i = ($opener + 1); $i < $next; $i++) { |
|||
if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) { |
|||
break; |
|||
} |
|||
|
|||
$phpcsFile->fixer->replaceToken($i, ''); |
|||
} |
|||
|
|||
$phpcsFile->fixer->addContent($opener, $phpcsFile->eolChar); |
|||
$phpcsFile->fixer->endChangeset(); |
|||
} |
|||
} |
|||
} else if ($tokens[$stackPtr]['code'] === T_WHILE) { |
|||
// Zero spaces after parenthesis closer. |
|||
$closer = $tokens[$stackPtr]['parenthesis_closer']; |
|||
$found = 0; |
|||
if ($tokens[($closer + 1)]['code'] === T_WHITESPACE) { |
|||
if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) { |
|||
$found = 'newline'; |
|||
} else { |
|||
$found = strlen($tokens[($closer + 1)]['content']); |
|||
} |
|||
} |
|||
|
|||
if ($found !== 0) { |
|||
$error = 'Expected 0 spaces before semicolon; %s found'; |
|||
$data = array($found); |
|||
$fix = $phpcsFile->addFixableError($error, $closer, 'SpaceBeforeSemicolon', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->replaceToken(($closer + 1), ''); |
|||
} |
|||
} |
|||
}//end if |
|||
|
|||
// Only want to check multi-keyword structures from here on. |
|||
if ($tokens[$stackPtr]['code'] === T_DO) { |
|||
if (isset($tokens[$stackPtr]['scope_closer']) === false) { |
|||
return; |
|||
} |
|||
|
|||
$closer = $tokens[$stackPtr]['scope_closer']; |
|||
} else if ($tokens[$stackPtr]['code'] === T_ELSE |
|||
|| $tokens[$stackPtr]['code'] === T_ELSEIF |
|||
|| $tokens[$stackPtr]['code'] === T_CATCH |
|||
) { |
|||
$closer = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); |
|||
if ($closer === false || $tokens[$closer]['code'] !== T_CLOSE_CURLY_BRACKET) { |
|||
return; |
|||
} |
|||
} else { |
|||
return; |
|||
} |
|||
|
|||
// Single space after closing brace. |
|||
if ($tokens[$stackPtr]['code'] === T_DO) { |
|||
$found = 1; |
|||
if ($tokens[($closer + 1)]['code'] !== T_WHITESPACE) { |
|||
$found = 0; |
|||
} else if ($tokens[($closer + 1)]['content'] !== ' ') { |
|||
if (strpos($tokens[($closer + 1)]['content'], $phpcsFile->eolChar) !== false) { |
|||
$found = 'newline'; |
|||
} else { |
|||
$found = strlen($tokens[($closer + 1)]['content']); |
|||
} |
|||
} |
|||
|
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 space after closing brace; %s found'; |
|||
$data = array($found); |
|||
$fix = $phpcsFile->addFixableError($error, $closer, 'SpaceAfterCloseBrace', $data); |
|||
if ($fix === true) { |
|||
if ($found === 0) { |
|||
$phpcsFile->fixer->addContent($closer, ' '); |
|||
} else { |
|||
$phpcsFile->fixer->replaceToken(($closer + 1), ' '); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Single newline after closing brace. |
|||
if ($tokens[$stackPtr]['code'] !== T_DO) { |
|||
for ($next = ($closer + 1); $next < $phpcsFile->numTokens; $next++) { |
|||
$code = $tokens[$next]['code']; |
|||
|
|||
if ($code === T_WHITESPACE) { |
|||
continue; |
|||
} |
|||
|
|||
// Skip all empty tokens on the same line as the closer. |
|||
if ($tokens[$next]['line'] === $tokens[$closer]['line'] |
|||
&& (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$code]) === true |
|||
|| $code === T_CLOSE_TAG) |
|||
) { |
|||
continue; |
|||
} |
|||
|
|||
// We found the first bit of a code, or a comment on the |
|||
// following line. |
|||
break; |
|||
} |
|||
|
|||
$found = ($tokens[$next]['line'] - $tokens[$closer]['line']); |
|||
if ($found !== 1) { |
|||
$error = 'Expected 1 newline after closing brace; %s found'; |
|||
$data = array($found); |
|||
$fix = $phpcsFile->addFixableError($error, $closer, 'NewlineAfterCloseBrace', $data); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->beginChangeset(); |
|||
for ($i = ($closer + 1); $i < $next; $i++) { |
|||
if ($found > 0 && $tokens[$i]['line'] === $tokens[$next]['line']) { |
|||
break; |
|||
} |
|||
|
|||
$phpcsFile->fixer->replaceToken($i, ''); |
|||
} |
|||
|
|||
$phpcsFile->fixer->addContent($closer, $phpcsFile->eolChar); |
|||
$phpcsFile->fixer->endChangeset(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
}//end process() |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
|
|||
class Barracuda_Sniffs_ControlStructures_NoInlineAssignmentSniff implements PHP_CodeSniffer_Sniff |
|||
{ |
|||
/** |
|||
* Returns an array of tokens this test wants to listen for. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function register() |
|||
{ |
|||
return array( |
|||
T_IF, |
|||
T_ELSEIF, |
|||
); |
|||
|
|||
} // end register() |
|||
|
|||
|
|||
/** |
|||
* Processes this sniff, when one of its tokens is encountered. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token in the |
|||
* stack passed in $tokens. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
$end_position = $tokens[$stackPtr]['parenthesis_closer']; |
|||
|
|||
// states: -1 = normal, 0 = start function call (probably), 1 = in function |
|||
$function = -1; |
|||
|
|||
for ($position = $stackPtr; $position < $end_position; $position++) |
|||
{ |
|||
if ($tokens[$position]['type'] == 'T_STRING') |
|||
{ |
|||
$function = 0; |
|||
continue; |
|||
} |
|||
|
|||
if ($function === 0) |
|||
{ |
|||
if ($tokens[$position]['type'] == 'T_OPEN_PARENTHESIS') |
|||
{ |
|||
$function = 1; |
|||
continue; |
|||
} |
|||
} |
|||
elseif ($function !== 1) |
|||
{ |
|||
$function = -1; |
|||
if ($tokens[$position]['type'] == 'T_EQUAL') |
|||
{ |
|||
$error = 'Inline assignment not allowed in if statements'; |
|||
$phpcsFile->addError($error, $stackPtr, 'IncDecLeft'); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,94 @@ |
|||
<?php |
|||
class Barracuda_Sniffs_Formatting_SpaceUnaryOperatorSniff implements PHP_CodeSniffer_Sniff |
|||
{ |
|||
|
|||
|
|||
/** |
|||
* Returns an array of tokens this test wants to listen for. |
|||
*/ |
|||
public function register() |
|||
{ |
|||
return array( |
|||
T_DEC, |
|||
T_INC, |
|||
T_MINUS, |
|||
T_PLUS, |
|||
T_BOOLEAN_NOT, |
|||
); |
|||
|
|||
} // end register() |
|||
|
|||
|
|||
/** |
|||
* Processes this test, when one of its tokens is encountered. |
|||
*/ |
|||
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) |
|||
{ |
|||
$tokens = $phpcsFile->getTokens(); |
|||
|
|||
// Check decrement / increment. |
|||
if ($tokens[$stackPtr]['code'] === T_DEC || $tokens[$stackPtr]['code'] === T_INC) |
|||
{ |
|||
$modifyLeft = substr($tokens[($stackPtr - 1)]['content'], 0, 1) === '$' || |
|||
$tokens[($stackPtr + 1)]['content'] === ';'; |
|||
|
|||
if ($modifyLeft === true && $tokens[($stackPtr - 1)]['code'] === T_WHITESPACE) |
|||
{ |
|||
$error = 'There must not be a single space before a unary operator statement'; |
|||
$phpcsFile->addError($error, $stackPtr, 'IncDecLeft'); |
|||
return; |
|||
} |
|||
|
|||
if ($modifyLeft === false && !in_array(substr($tokens[($stackPtr + 1)]['content'], 0, 1), array('$', ','))) |
|||
{ |
|||
$error = 'A unary operator statement must not be followed by a single space'; |
|||
$phpcsFile->addError($error, $stackPtr, 'IncDecRight'); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Check "!" operator. |
|||
if ($tokens[$stackPtr]['code'] === T_BOOLEAN_NOT && $tokens[$stackPtr + 1]['code'] === T_WHITESPACE) |
|||
{ |
|||
$error = 'A unary operator statement must not be followed by a space'; |
|||
$phpcsFile->addError($error, $stackPtr, 'BooleanNot'); |
|||
return; |
|||
} |
|||
|
|||
// Find the last syntax item to determine if this is an unary operator. |
|||
$lastSyntaxItem = $phpcsFile->findPrevious( |
|||
array(T_WHITESPACE), |
|||
$stackPtr - 1, |
|||
($tokens[$stackPtr]['column']) * -1, |
|||
true, |
|||
null, |
|||
true |
|||
); |
|||
$operatorSuffixAllowed = in_array( |
|||
$tokens[$lastSyntaxItem]['code'], |
|||
array( |
|||
T_LNUMBER, |
|||
T_DNUMBER, |
|||
T_CLOSE_PARENTHESIS, |
|||
T_CLOSE_CURLY_BRACKET, |
|||
T_CLOSE_SQUARE_BRACKET, |
|||
T_VARIABLE, |
|||
T_STRING, |
|||
) |
|||
); |
|||
|
|||
// Check plus / minus value assignments or comparisons. |
|||
if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS) |
|||
{ |
|||
if ($operatorSuffixAllowed === false |
|||
&& $tokens[($stackPtr + 1)]['code'] === T_WHITESPACE |
|||
) |
|||
{ |
|||
$error = 'A unary operator statement must not be followed by a space'; |
|||
$phpcsFile->addError($error, $stackPtr); |
|||
} |
|||
} |
|||
|
|||
} // end process() |
|||
// end class |
|||
} |
@ -0,0 +1,346 @@ |
|||
<?php |
|||
/** |
|||
* Barracuda_Sniffs_Functions_MultiLineFunctionDeclarationSniff. |
|||
* |
|||
* PHP version 5 |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
|
|||
if (class_exists('PEAR_Sniffs_Functions_FunctionDeclarationSniff', true) === false) { |
|||
$error = 'Class PEAR_Sniffs_Functions_FunctionDeclarationSniff not found'; |
|||
throw new PHP_CodeSniffer_Exception($error); |
|||
} |
|||
|
|||
/** |
|||
* Barracuda_Sniffs_Functions_MultiLineFunctionDeclarationSniff. |
|||
* |
|||
* Ensure single and multi-line function declarations are defined correctly. |
|||
* |
|||
* @category PHP |
|||
* @package PHP_CodeSniffer |
|||
* @author Greg Sherwood <gsherwood@squiz.net> |
|||
* @copyright 2006-2012 Squiz Pty Ltd (ABN 77 084 670 600) |
|||
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence |
|||
* @version Release: 1.5.0RC4 |
|||
* @link http://pear.php.net/package/PHP_CodeSniffer |
|||
*/ |
|||
class Barracuda_Sniffs_Functions_FunctionDeclarationSniff extends PEAR_Sniffs_Functions_FunctionDeclarationSniff |
|||
{ |
|||
|
|||
|
|||
/** |
|||
* Processes multi-line declarations. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param array $tokens The stack of tokens that make up |
|||
* the file. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function processMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens) |
|||
{ |
|||
// We need to work out how far indented the function |
|||
// declaration itself is, so we can work out how far to |
|||
// indent parameters. |
|||
$functionIndent = 0; |
|||
for ($i = ($stackPtr - 1); $i >= 0; $i--) { |
|||
if ($tokens[$i]['line'] !== $tokens[$stackPtr]['line']) { |
|||
$i++; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if ($tokens[$i]['code'] === T_WHITESPACE) { |
|||
$functionIndent = strlen($tokens[$i]['content']); |
|||
} |
|||
|
|||
// The closing parenthesis must be on a new line, even |
|||
// when checking abstract function definitions. |
|||
$closeBracket = $tokens[$stackPtr]['parenthesis_closer']; |
|||
$prev = $phpcsFile->findPrevious( |
|||
T_WHITESPACE, |
|||
($closeBracket - 1), |
|||
null, |
|||
true |
|||
); |
|||
|
|||
if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { |
|||
if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { |
|||
$error = 'The closing parenthesis of a multi-line function declaration must be on a new line'; |
|||
$fix = $phpcsFile->addFixableError($error, $closeBracket, 'CloseBracketLine'); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->addNewlineBefore($closeBracket); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// If this is a closure and is using a USE statement, the closing |
|||
// parenthesis we need to look at from now on is the closing parenthesis |
|||
// of the USE statement. |
|||
if ($tokens[$stackPtr]['code'] === T_CLOSURE) { |
|||
$use = $phpcsFile->findNext(T_USE, ($closeBracket + 1), $tokens[$stackPtr]['scope_opener']); |
|||
if ($use !== false) { |
|||
$open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1)); |
|||
$closeBracket = $tokens[$open]['parenthesis_closer']; |
|||
|
|||
$prev = $phpcsFile->findPrevious( |
|||
T_WHITESPACE, |
|||
($closeBracket - 1), |
|||
null, |
|||
true |
|||
); |
|||
|
|||
if ($tokens[$closeBracket]['line'] !== $tokens[$tokens[$closeBracket]['parenthesis_opener']]['line']) { |
|||
if ($tokens[$prev]['line'] === $tokens[$closeBracket]['line']) { |
|||
$error = 'The closing parenthesis of a multi-line use declaration must be on a new line'; |
|||
$fix = $phpcsFile->addFixableError($error, $closeBracket, 'UseCloseBracketLine'); |
|||
if ($fix === true) { |
|||
$phpcsFile->fixer->addNewlineBefore($closeBracket); |
|||
} |
|||
} |
|||
} |
|||
}//end if |
|||
}//end if |
|||
|
|||
// Each line between the parenthesis should be indented 4 spaces. |
|||
$openBracket = $tokens[$stackPtr]['parenthesis_opener']; |
|||
$lastLine = $tokens[$openBracket]['line']; |
|||
for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { |
|||
if ($tokens[$i]['line'] !== $lastLine) { |
|||
if ($i === $tokens[$stackPtr]['parenthesis_closer'] |
|||
|| ($tokens[$i]['code'] === T_WHITESPACE |
|||
&& (($i + 1) === $closeBracket |
|||
|| ($i + 1) === $tokens[$stackPtr]['parenthesis_closer'])) |
|||
) { |
|||
// Closing braces need to be indented to the same level |
|||
// as the function. |
|||
$expectedIndent = $functionIndent; |
|||
} else { |
|||
$expectedIndent = ($functionIndent + $this->indent); |
|||
} |
|||
|
|||
// We changed lines, so this should be a whitespace indent token. |
|||
if ($tokens[$i]['code'] !== T_WHITESPACE) { |
|||
$foundIndent = 0; |
|||
} else { |
|||
$foundIndent = strlen($tokens[$i]['content']); |
|||
} |
|||
|
|||
if ($expectedIndent !== $foundIndent) { |
|||
$error = 'Multi-line function declaration not indented correctly; expected %s spaces but found %s'; |
|||
$data = array( |
|||
$expectedIndent, |
|||
$foundIndent, |
|||
); |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, $i, 'Indent', $data); |
|||
if ($fix === true) { |
|||
$spaces = str_repeat(' ', $expectedIndent); |
|||
if ($foundIndent === 0) { |
|||
$phpcsFile->fixer->addContentBefore($i, $spaces); |
|||
} else { |
|||
$phpcsFile->fixer->replaceToken($i, $spaces); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$lastLine = $tokens[$i]['line']; |
|||
}//end if |
|||
|
|||
if ($tokens[$i]['code'] === T_ARRAY || $tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { |
|||
// Skip arrays as they have their own indentation rules. |
|||
if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) { |
|||
$i = $tokens[$i]['bracket_closer']; |
|||
} else { |
|||
$i = $tokens[$i]['parenthesis_closer']; |
|||
} |
|||
|
|||
$lastLine = $tokens[$i]['line']; |
|||
continue; |
|||
} |
|||
}//end for |
|||
|
|||
if (isset($tokens[$stackPtr]['scope_opener']) === true) { |
|||
|
|||
// any scope opener, we get the next token (should be EOL) |
|||
$next = $tokens[($closeBracket + 1)]; |
|||
|
|||
// if the token is EOL, then no error |
|||
if ($next['content'] === $phpcsFile->eolChar) |
|||
{ |
|||
$length = -1; |
|||
} elseif ($next['code'] == T_OPEN_CURLY_BRACKET) { |
|||
$length = 0; |
|||
} else { |
|||
$length = strlen($next['content']); |
|||
} |
|||
|
|||
// any length means a problem, even zero |
|||
if ($length >= 0) { |
|||
$data = array($length); |
|||
$code = 'NewLineBeforeOpenBrace'; |
|||
|
|||
$error = 'There must be a newline before the opening brace of a multi-line function declaration; found '; |
|||
|
|||
// if whitespace, then report it |
|||
if ($length > 0) { |
|||
$error .= '%s spaces'; |
|||
$code = 'SpaceBeforeOpenBrace'; |
|||
} |
|||
// otherwise, no space but still brace on same line |
|||
else |
|||
{ |
|||
$error .= ' opening brace'; |
|||
} |
|||
|
|||
$fix = $phpcsFile->addFixableError($error, ($closeBracket + 1), $code, $data); |
|||
if ($fix === true) { |
|||
|
|||
// remove whitespace |
|||
if ($length > 0) |
|||
{ |
|||
$phpcsFile->fixer->replaceToken($closeBracket + 1, ''); |
|||
} |
|||
|
|||
// add the EOL token |
|||
$phpcsFile->fixer->addContent($closeBracket, $phpcsFile->eolChar); |
|||
} |
|||
|
|||
return; |
|||
}//end if |
|||
|
|||
// And just in case they do something funny before the brace... |
|||
$next = $phpcsFile->findNext( |
|||
T_WHITESPACE, |
|||
($closeBracket + 1), |
|||
null, |
|||
true |
|||
); |
|||
|
|||
if ($next !== false && $tokens[$next]['code'] !== T_OPEN_CURLY_BRACKET) { |
|||
$error = 'There must be a single space between the closing parenthesis and the opening brace of a multi-line function declaration'; |
|||
$phpcsFile->addError($error, $next, 'NoSpaceBeforeOpenBrace'); |
|||
} |
|||
}//end if |
|||
|
|||
$openBracket = $tokens[$stackPtr]['parenthesis_opener']; |
|||
$this->processBracket($phpcsFile, $openBracket, $tokens, 'function'); |
|||
|
|||
if ($tokens[$stackPtr]['code'] !== T_CLOSURE) { |
|||
return; |
|||
} |
|||
|
|||
$use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); |
|||
if ($use === false) { |
|||
return; |
|||
} |
|||
|
|||
$openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null); |
|||
$this->processBracket($phpcsFile, $openBracket, $tokens, 'use'); |
|||
|
|||
// Also check spacing. |
|||
if ($tokens[($use - 1)]['code'] === T_WHITESPACE) { |
|||
$gap = strlen($tokens[($use - 1)]['content']); |
|||
} else { |
|||
$gap = 0; |
|||
} |
|||
|
|||
}//end processMultiLineDeclaration() |
|||
|
|||
|
|||
/** |
|||
* Processes the contents of a single set of brackets. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $openBracket The position of the open bracket |
|||
* in the stack passed in $tokens. |
|||
* @param array $tokens The stack of tokens that make up |
|||
* the file. |
|||
* @param string $type The type of the token the brackets |
|||
* belong to (function or use). |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket, $tokens, $type='function') |
|||
{ |
|||
$errorPrefix = ''; |
|||
if ($type === 'use') { |
|||
$errorPrefix = 'Use'; |
|||
} |
|||
|
|||
$closeBracket = $tokens[$openBracket]['parenthesis_closer']; |
|||
|
|||
// The open bracket should be the last thing on the line. |
|||
if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) { |
|||
$next = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true); |
|||
if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) { |
|||
$error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket'; |
|||
$phpcsFile->addError($error, $next, $errorPrefix.'FirstParamSpacing'); |
|||
} |
|||
} |
|||
|
|||
// Each line between the brackets should contain a single parameter. |
|||
$lastCommaLine = null; |
|||
for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { |
|||
// Skip brackets, like arrays, as they can contain commas. |
|||
if (isset($tokens[$i]['parenthesis_opener']) === true) { |
|||
$i = $tokens[$i]['parenthesis_closer']; |
|||
continue; |
|||
} |
|||
|
|||
if ($tokens[$i]['code'] === T_COMMA) { |
|||
if ($lastCommaLine !== null && $lastCommaLine === $tokens[$i]['line']) { |
|||
$error = 'Multi-line '.$type.' declarations must define one parameter per line'; |
|||
$phpcsFile->addError($error, $i, $errorPrefix.'OneParamPerLine'); |
|||
} else { |
|||
// Comma must be the last thing on the line. |
|||
$next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); |
|||
if ($tokens[$next]['line'] !== ($tokens[$i]['line'] + 1)) { |
|||
$error = 'Commas in multi-line '.$type.' declarations must be the last content on a line'; |
|||
$phpcsFile->addError($error, $next, $errorPrefix.'ContentAfterComma'); |
|||
} |
|||
} |
|||
|
|||
$lastCommaLine = $tokens[$i]['line']; |
|||
} |
|||
} |
|||
|
|||
}//end processBracket() |
|||
|
|||
/** |
|||
* Processes single-line declarations. |
|||
* |
|||
* Just uses the Generic BSD-Allman brace sniff. |
|||
* |
|||
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned. |
|||
* @param int $stackPtr The position of the current token |
|||
* in the stack passed in $tokens. |
|||
* @param array $tokens The stack of tokens that make up |
|||
* the file. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function processSingleLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens) |
|||
{ |
|||
if (class_exists('Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff', true) === false) { |
|||
throw new PHP_CodeSniffer_Exception('Class Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff not found'); |
|||
} |
|||
|
|||
$sniff = new Generic_Sniffs_Functions_OpeningFunctionBraceBsdAllmanSniff(); |
|||
|
|||
$sniff->process($phpcsFile, $stackPtr); |
|||
|
|||
}//end processSingleLineDeclaration() |
|||
|
|||
|
|||
}//end class |
|||
|
|||
?> |
@ -0,0 +1,48 @@ |
|||
<?xml version="1.0"?> |
|||
<ruleset name="Barracuda"> |
|||
|
|||
<description>PSR2 with Barracuda exceptions</description> |
|||
|
|||
<rule ref="PSR2"> |
|||
<!-- Exclude camel caps class name checking --> |
|||
<exclude name="Squiz.ControlStructures.ControlSignature"/> |
|||
<!-- Exclude camel caps function name checking --> |
|||
<exclude name="Generic.NamingConventions.CamelCapsFunctionName"/> |
|||
<!-- Exclude camel caps class method name checking --> |
|||
<exclude name="PSR1.Methods.CamelCapsMethodName"/> |
|||
<!-- Exclude camel caps class name checking --> |
|||
<exclude name="Squiz.Classes.ValidClassName"/> |
|||
<!-- Exclude class namespace check --> |
|||
<exclude name="PSR1.Classes.ClassDeclaration"/> |
|||
<!-- Exclude multi line function --> |
|||
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration"/> |
|||
<!-- Exclude default function sniff --> |
|||
<exclude name="Squiz.Functions.FunctionDeclaration"/> |
|||
<!-- Exclude spaces instead of tabs --> |
|||
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/> |
|||
<!-- Exclude spacing around parenthesis checks --> |
|||
<exclude name="PSR2.ControlStructures.ControlStructureSpacing"/> |
|||
</rule> |
|||
|
|||
<!-- Configure for concatenation operator spacing --> |
|||
<rule ref="Squiz.Strings.ConcatenationSpacing"> |
|||
<properties> |
|||
<property name="spacing" value="1" /> |
|||
<property name="ignoreNewlines" value="true" /> |
|||
</properties> |
|||
</rule> |
|||
|
|||
<!-- Configure for commenting --> |
|||
<rule ref="Squiz.Commenting.FunctionCommentThrowTag" /> |
|||
<rule ref="Squiz.Commenting.ClassComment" /> |
|||
|
|||
<!-- Configure for tabs --> |
|||
<!-- <arg name="tab-width" value="4"/> |
|||
<rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/> |
|||
<rule ref="Generic.WhiteSpace.ScopeIndent"> |
|||
<properties> |
|||
<property name="indent" value="4"/> |
|||
<property name="tabIndent" value="true"/> |
|||
</properties> |
|||
</rule> --> |
|||
</ruleset> |
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2013 Barracuda Networks, Inc. |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in |
|||
all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
@ -0,0 +1,20 @@ |
|||
# PHP Fork Daemon |
|||
A library to make setup and management of forking daemons in PHP easy. |
|||
|
|||
## Features |
|||
- Easy management of PHP forks |
|||
- Return result of children by callback or polling parent for results |
|||
- Splitting work units into buckets |
|||
- Preforking callbacks to manage resources before forking |
|||
- Dynamic setting of number of children / work per child |
|||
|
|||
## Usage |
|||
Check out the examples in the examples directory |
|||
``php example/blocking.php`` |
|||
|
|||
## Caveats |
|||
- You need to specify ``declare(ticks=1);`` before inclusion of the fork-daemon library, otherwise signals wont be handled. This *must* be done in the main PHP file, as ``declare(ticks=N);`` only works for the file in which it is declared and the files which that file includes. Reference: [PHP Documentation](http://php.net/manual/en/control-structures.declare.php#control-structures.declare.ticks) |
|||
|
|||
## License |
|||
Copyright 2013 Barracuda Networks, Inc. |
|||
Licensed under the MIT License |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"name": "barracuda/forkdaemon-php", |
|||
"type": "library", |
|||
"description": "A library to make setup and management of forking daemons in PHP easy.", |
|||
"keywords": ["forking", "daemons", "php"], |
|||
"homepage": "https://github.com/barracudanetworks/forkdaemon-php", |
|||
"license": "MIT", |
|||
"require": { |
|||
"php": ">=5.3.0" |
|||
}, |
|||
"autoload": { |
|||
"classmap": [ "fork_daemon.php" ] |
|||
} |
|||
} |
@ -0,0 +1,63 @@ |
|||
<?php |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(5); |
|||
$server->max_work_per_child_set(3); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
|
|||
test_blocking(); |
|||
|
|||
function test_blocking() |
|||
{ |
|||
global $server; |
|||
|
|||
echo "Adding 10 units of work\n"; |
|||
|
|||
$data_set = array(); |
|||
for($i=0; $i<10; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$server->addwork($data_set); |
|||
|
|||
/* process work blocking mode */ |
|||
$server->process_work(true); |
|||
|
|||
echo "Adding 15 more units of work\n"; |
|||
|
|||
$data_set = array(); |
|||
for($i=10; $i<25; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$server->addwork($data_set); |
|||
|
|||
/* process work blocking mode */ |
|||
$server->process_work(true); |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
sleep(rand(4,8)); |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
@ -0,0 +1,78 @@ |
|||
<?php |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(5); |
|||
$server->max_work_per_child_set(3); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
|
|||
test_bucket(); |
|||
|
|||
function test_bucket() |
|||
{ |
|||
global $server; |
|||
|
|||
define("BUCKET1", 1); |
|||
define("BUCKET2", 2); |
|||
|
|||
$server->add_bucket(BUCKET1); |
|||
$server->add_bucket(BUCKET2); |
|||
$server->max_children_set(2, BUCKET1); |
|||
$server->max_children_set(5, BUCKET2); |
|||
|
|||
$data_set = array(); |
|||
for($i=0; $i<100; $i++) $data_set[] = $i; |
|||
|
|||
/* add work to bucket 1 */ |
|||
shuffle($data_set); |
|||
$server->addwork($data_set, "", BUCKET1); |
|||
|
|||
/* add work to bucket 2 */ |
|||
shuffle($data_set); |
|||
$server->addwork($data_set, "", BUCKET2); |
|||
|
|||
/* wait until all work allocated */ |
|||
while ($server->work_sets_count(BUCKET1) > 0 || $server->work_sets_count(BUCKET2) > 0) |
|||
{ |
|||
echo "work set count(1): " . $server->work_sets_count(BUCKET1) . ", count(2): " . $server->work_sets_count(BUCKET2) . "\n"; |
|||
if ($server->work_sets_count(BUCKET1) > 0) $server->process_work(false, BUCKET1); |
|||
if ($server->work_sets_count(BUCKET2) > 0) $server->process_work(false, BUCKET2); |
|||
sleep(1); |
|||
} |
|||
|
|||
/* wait until all children finish */ |
|||
while ($server->children_running() > 0) |
|||
{ |
|||
echo "waiting for " . $server->children_running() . " children to finish\n"; |
|||
sleep(1); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
sleep(rand(4,8)); |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
@ -0,0 +1,81 @@ |
|||
<?php |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(5); |
|||
$server->max_work_per_child_set(3); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
|
|||
test_identifier(); |
|||
|
|||
function test_identifier() |
|||
{ |
|||
global $server; |
|||
|
|||
$server->child_single_work_item_set(true); |
|||
$server->max_work_per_child_set(1); |
|||
|
|||
echo "Adding 100 units of work\n"; |
|||
|
|||
/* add work */ |
|||
$data_set = array(); |
|||
for($i=0; $i<100; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$data_set = array_chunk($data_set, 3); |
|||
|
|||
$i = 0; |
|||
foreach ($data_set as $item) |
|||
{ |
|||
$server->addwork($item, "IDn$i"); |
|||
$i++; |
|||
} |
|||
|
|||
echo "Processing work in non-blocking mode\n"; |
|||
|
|||
/* process work non blocking mode */ |
|||
$server->process_work(false); |
|||
|
|||
/* wait until all work allocated */ |
|||
while ($server->work_sets_count() > 0) |
|||
{ |
|||
echo "work set count: " . $server->work_sets_count() . "\n"; |
|||
$server->process_work(false); |
|||
sleep(1); |
|||
} |
|||
|
|||
/* wait until all children finish */ |
|||
while ($server->children_running() > 0) |
|||
{ |
|||
echo "waiting for " . $server->children_running() . " children to finish\n"; |
|||
sleep(1); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
sleep(rand(4,8)); |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
@ -0,0 +1,71 @@ |
|||
<?php |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(5); |
|||
$server->max_work_per_child_set(3); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
|
|||
test_nonblocking(); |
|||
|
|||
function test_nonblocking() |
|||
{ |
|||
global $server; |
|||
|
|||
echo "Adding 100 units of work\n"; |
|||
|
|||
/* add work */ |
|||
$data_set = array(); |
|||
for($i=0; $i<100; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$server->addwork($data_set); |
|||
|
|||
echo "Processing work in non-blocking mode\n"; |
|||
|
|||
/* process work non blocking mode */ |
|||
$server->process_work(false); |
|||
|
|||
/* wait until all work allocated */ |
|||
while ($server->work_sets_count() > 0) |
|||
{ |
|||
echo "work set count: " . $server->work_sets_count() . "\n"; |
|||
$server->process_work(false); |
|||
sleep(1); |
|||
} |
|||
|
|||
/* wait until all children finish */ |
|||
while ($server->children_running() > 0) |
|||
{ |
|||
echo "waiting for " . $server->children_running() . " children to finish\n"; |
|||
sleep(1); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
sleep(rand(4,8)); |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
@ -0,0 +1,86 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* Sample of passing in work and getting back results using a callback |
|||
*/ |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(5); |
|||
$server->max_work_per_child_set(3); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
$server->register_parent_results("process_results"); |
|||
|
|||
test_nonblocking(); |
|||
|
|||
function test_nonblocking() |
|||
{ |
|||
global $server; |
|||
|
|||
echo "Adding 100 units of work\n"; |
|||
|
|||
/* add work */ |
|||
$data_set = array(); |
|||
for($i=0; $i<100; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$server->addwork($data_set); |
|||
|
|||
echo "Processing work in non-blocking mode\n"; |
|||
|
|||
/* process work non blocking mode */ |
|||
$server->process_work(false); |
|||
|
|||
/* wait until all work allocated */ |
|||
while ($server->work_sets_count() > 0) |
|||
{ |
|||
echo "work set count: " . $server->work_sets_count() . "\n"; |
|||
$server->process_work(false); |
|||
sleep(1); |
|||
} |
|||
|
|||
/* wait until all children finish */ |
|||
while ($server->children_running() > 0) |
|||
{ |
|||
echo "waiting for " . $server->children_running() . " children to finish\n"; |
|||
sleep(1); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
function process_results($results, $identifier = "") |
|||
{ |
|||
echo "Results came back: " . implode(",", $results) . ($identifier == "" ? "" : " (id:$identifier)") . PHP_EOL; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
|
|||
$result = array_sum($data_set); |
|||
sleep(rand(1,3)); |
|||
|
|||
// return results |
|||
return $result; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
@ -0,0 +1,92 @@ |
|||
<?php |
|||
|
|||
/** |
|||
* Sample of passing in work and getting back results using a call at the |
|||
* end of the child processing |
|||
*/ |
|||
|
|||
declare(ticks=1); |
|||
|
|||
require_once(__DIR__ . '/../fork_daemon.php'); |
|||
|
|||
/* setup forking daemon */ |
|||
$server = new fork_daemon(); |
|||
$server->max_children_set(100); |
|||
$server->max_work_per_child_set(3); |
|||
$server->store_result_set(true); |
|||
$server->register_child_run("process_child_run"); |
|||
$server->register_parent_child_exit("process_child_exit"); |
|||
$server->register_logging("logger", fork_daemon::LOG_LEVEL_ALL); |
|||
// no callback with this method since we check results at the end |
|||
|
|||
test_nonblocking(); |
|||
|
|||
// since deferred results, check at the end |
|||
$results = $server->get_all_results(); |
|||
var_dump($results); |
|||
echo "Sum: " . array_sum($results) . PHP_EOL; |
|||
echo "Count: " . count($results) . PHP_EOL; |
|||
|
|||
function test_nonblocking() |
|||
{ |
|||
global $server; |
|||
|
|||
echo "Adding 100 units of work\n"; |
|||
|
|||
/* add work */ |
|||
$data_set = array(); |
|||
for($i=0; $i<100; $i++) $data_set[] = $i; |
|||
shuffle($data_set); |
|||
$server->addwork($data_set); |
|||
|
|||
echo "Processing work in non-blocking mode\n"; |
|||
echo "Sum: " . array_sum($data_set) . PHP_EOL; |
|||
|
|||
/* process work non blocking mode */ |
|||
$server->process_work(false); |
|||
|
|||
/* wait until all work allocated */ |
|||
while ($server->work_sets_count() > 0) |
|||
{ |
|||
echo "work set count: " . $server->work_sets_count() . "\n"; |
|||
$server->process_work(false); |
|||
sleep(1); |
|||
} |
|||
|
|||
/* wait until all children finish */ |
|||
while ($server->children_running() > 0) |
|||
{ |
|||
echo "waiting for " . $server->children_running() . " children to finish\n"; |
|||
sleep(1); |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* CALLBACK FUNCTIONS |
|||
*/ |
|||
|
|||
/* registered call back function */ |
|||
function process_child_run($data_set, $identifier = "") |
|||
{ |
|||
echo "I'm child working on: " . implode(",", $data_set) . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
|
|||
sleep(rand(1,3)); |
|||
|
|||
// just do a sum and return it as the result |
|||
$result = array_sum($data_set); |
|||
|
|||
// return results |
|||
return $result; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function process_child_exit($pid, $identifier = "") |
|||
{ |
|||
echo "Child $pid just finished" . ($identifier == "" ? "" : " (id:$identifier)") . "\n"; |
|||
} |
|||
|
|||
/* registered call back function */ |
|||
function logger($message) |
|||
{ |
|||
echo "logger: " . $message . PHP_EOL; |
|||
} |
File diff suppressed because it is too large
@ -0,0 +1 @@ |
|||
../lithium/console/li3 |
@ -0,0 +1 @@ |
|||
../robmorgan/phinx/bin/phinx |
@ -0,0 +1,22 @@ |
|||
language: php |
|||
sudo: false |
|||
|
|||
matrix: |
|||
include: |
|||
- php: 7.0 |
|||
|
|||
cache: |
|||
directories: |
|||
- "$HOME/.composer/cache" |
|||
|
|||
install: |
|||
- composer update --prefer-dist --prefer-stable |
|||
|
|||
script: |
|||
- ./vendor/bin/phpunit --coverage-clover=coverage.xml |
|||
|
|||
after_success: |
|||
- pip install --user codecov && codecov |
|||
|
|||
notifications: |
|||
email: false |
@ -0,0 +1,558 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
use Cache\Adapter\Common\Exception\CacheException; |
|||
use Cache\Adapter\Common\Exception\CachePoolException; |
|||
use Cache\Adapter\Common\Exception\InvalidArgumentException; |
|||
use Psr\Cache\CacheItemInterface; |
|||
use Psr\Log\LoggerAwareInterface; |
|||
use Psr\Log\LoggerInterface; |
|||
use Psr\SimpleCache\CacheInterface; |
|||
|
|||
/** |
|||
* @author Aaron Scherer <aequasi@gmail.com> |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
abstract class AbstractCachePool implements PhpCachePool, LoggerAwareInterface, CacheInterface |
|||
{ |
|||
const SEPARATOR_TAG = '!'; |
|||
|
|||
/** |
|||
* @type LoggerInterface |
|||
*/ |
|||
private $logger; |
|||
|
|||
/** |
|||
* @type PhpCacheItem[] deferred |
|||
*/ |
|||
protected $deferred = []; |
|||
|
|||
/** |
|||
* @param PhpCacheItem $item |
|||
* @param int|null $ttl seconds from now |
|||
* |
|||
* @return bool true if saved |
|||
*/ |
|||
abstract protected function storeItemInCache(PhpCacheItem $item, $ttl); |
|||
|
|||
/** |
|||
* Fetch an object from the cache implementation. |
|||
* |
|||
* If it is a cache miss, it MUST return [false, null, [], null] |
|||
* |
|||
* @param string $key |
|||
* |
|||
* @return array with [isHit, value, tags[], expirationTimestamp] |
|||
*/ |
|||
abstract protected function fetchObjectFromCache($key); |
|||
|
|||
/** |
|||
* Clear all objects from cache. |
|||
* |
|||
* @return bool false if error |
|||
*/ |
|||
abstract protected function clearAllObjectsFromCache(); |
|||
|
|||
/** |
|||
* Remove one object from cache. |
|||
* |
|||
* @param string $key |
|||
* |
|||
* @return bool |
|||
*/ |
|||
abstract protected function clearOneObjectFromCache($key); |
|||
|
|||
/** |
|||
* Get an array with all the values in the list named $name. |
|||
* |
|||
* @param string $name |
|||
* |
|||
* @return array |
|||
*/ |
|||
abstract protected function getList($name); |
|||
|
|||
/** |
|||
* Remove the list. |
|||
* |
|||
* @param string $name |
|||
* |
|||
* @return bool |
|||
*/ |
|||
abstract protected function removeList($name); |
|||
|
|||
/** |
|||
* Add a item key on a list named $name. |
|||
* |
|||
* @param string $name |
|||
* @param string $key |
|||
*/ |
|||
abstract protected function appendListItem($name, $key); |
|||
|
|||
/** |
|||
* Remove an item from the list. |
|||
* |
|||
* @param string $name |
|||
* @param string $key |
|||
*/ |
|||
abstract protected function removeListItem($name, $key); |
|||
|
|||
/** |
|||
* Make sure to commit before we destruct. |
|||
*/ |
|||
public function __destruct() |
|||
{ |
|||
$this->commit(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getItem($key) |
|||
{ |
|||
$this->validateKey($key); |
|||
if (isset($this->deferred[$key])) { |
|||
/** @type CacheItem $item */ |
|||
$item = clone $this->deferred[$key]; |
|||
$item->moveTagsToPrevious(); |
|||
|
|||
return $item; |
|||
} |
|||
|
|||
$func = function () use ($key) { |
|||
try { |
|||
return $this->fetchObjectFromCache($key); |
|||
} catch (\Exception $e) { |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
}; |
|||
|
|||
return new CacheItem($key, $func); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getItems(array $keys = []) |
|||
{ |
|||
$items = []; |
|||
foreach ($keys as $key) { |
|||
$items[$key] = $this->getItem($key); |
|||
} |
|||
|
|||
return $items; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function hasItem($key) |
|||
{ |
|||
try { |
|||
return $this->getItem($key)->isHit(); |
|||
} catch (\Exception $e) { |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function clear() |
|||
{ |
|||
// Clear the deferred items |
|||
$this->deferred = []; |
|||
|
|||
try { |
|||
return $this->clearAllObjectsFromCache(); |
|||
} catch (\Exception $e) { |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function deleteItem($key) |
|||
{ |
|||
try { |
|||
return $this->deleteItems([$key]); |
|||
} catch (\Exception $e) { |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function deleteItems(array $keys) |
|||
{ |
|||
$deleted = true; |
|||
foreach ($keys as $key) { |
|||
$this->validateKey($key); |
|||
|
|||
// Delete form deferred |
|||
unset($this->deferred[$key]); |
|||
|
|||
// We have to commit here to be able to remove deferred hierarchy items |
|||
$this->commit(); |
|||
$this->preRemoveItem($key); |
|||
|
|||
if (!$this->clearOneObjectFromCache($key)) { |
|||
$deleted = false; |
|||
} |
|||
} |
|||
|
|||
return $deleted; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function save(CacheItemInterface $item) |
|||
{ |
|||
if (!$item instanceof PhpCacheItem) { |
|||
$e = new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement PhpCacheItem.'); |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
|
|||
$this->removeTagEntries($item); |
|||
$this->saveTags($item); |
|||
$timeToLive = null; |
|||
if (null !== $timestamp = $item->getExpirationTimestamp()) { |
|||
$timeToLive = $timestamp - time(); |
|||
|
|||
if ($timeToLive < 0) { |
|||
return $this->deleteItem($item->getKey()); |
|||
} |
|||
} |
|||
|
|||
try { |
|||
return $this->storeItemInCache($item, $timeToLive); |
|||
} catch (\Exception $e) { |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function saveDeferred(CacheItemInterface $item) |
|||
{ |
|||
$this->deferred[$item->getKey()] = $item; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function commit() |
|||
{ |
|||
$saved = true; |
|||
foreach ($this->deferred as $item) { |
|||
if (!$this->save($item)) { |
|||
$saved = false; |
|||
} |
|||
} |
|||
$this->deferred = []; |
|||
|
|||
return $saved; |
|||
} |
|||
|
|||
/** |
|||
* @param string $key |
|||
* |
|||
* @throws InvalidArgumentException |
|||
*/ |
|||
protected function validateKey($key) |
|||
{ |
|||
if (!is_string($key)) { |
|||
$e = new InvalidArgumentException(sprintf( |
|||
'Cache key must be string, "%s" given', gettype($key) |
|||
)); |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
if (!isset($key[0])) { |
|||
$e = new InvalidArgumentException('Cache key cannot be an empty string'); |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) { |
|||
$e = new InvalidArgumentException(sprintf( |
|||
'Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:', |
|||
$key |
|||
)); |
|||
$this->handleException($e, __FUNCTION__); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param LoggerInterface $logger |
|||
*/ |
|||
public function setLogger(LoggerInterface $logger) |
|||
{ |
|||
$this->logger = $logger; |
|||
} |
|||
|
|||
/** |
|||
* Logs with an arbitrary level if the logger exists. |
|||
* |
|||
* @param mixed $level |
|||
* @param string $message |
|||
* @param array $context |
|||
*/ |
|||
protected function log($level, $message, array $context = []) |
|||
{ |
|||
if ($this->logger !== null) { |
|||
$this->logger->log($level, $message, $context); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Log exception and rethrow it. |
|||
* |
|||
* @param \Exception $e |
|||
* @param string $function |
|||
* |
|||
* @throws CachePoolException |
|||
*/ |
|||
private function handleException(\Exception $e, $function) |
|||
{ |
|||
$level = 'alert'; |
|||
if ($e instanceof InvalidArgumentException) { |
|||
$level = 'warning'; |
|||
} |
|||
|
|||
$this->log($level, $e->getMessage(), ['exception' => $e]); |
|||
if (!$e instanceof CacheException) { |
|||
$e = new CachePoolException(sprintf('Exception thrown when executing "%s". ', $function), 0, $e); |
|||
} |
|||
|
|||
throw $e; |
|||
} |
|||
|
|||
/** |
|||
* @param array $tags |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public function invalidateTags(array $tags) |
|||
{ |
|||
$itemIds = []; |
|||
foreach ($tags as $tag) { |
|||
$itemIds = array_merge($itemIds, $this->getList($this->getTagKey($tag))); |
|||
} |
|||
|
|||
// Remove all items with the tag |
|||
$success = $this->deleteItems($itemIds); |
|||
|
|||
if ($success) { |
|||
// Remove the tag list |
|||
foreach ($tags as $tag) { |
|||
$this->removeList($this->getTagKey($tag)); |
|||
$l = $this->getList($this->getTagKey($tag)); |
|||
} |
|||
} |
|||
|
|||
return $success; |
|||
} |
|||
|
|||
public function invalidateTag($tag) |
|||
{ |
|||
return $this->invalidateTags([$tag]); |
|||
} |
|||
|
|||
/** |
|||
* @param PhpCacheItem $item |
|||
*/ |
|||
protected function saveTags(PhpCacheItem $item) |
|||
{ |
|||
$tags = $item->getTags(); |
|||
foreach ($tags as $tag) { |
|||
$this->appendListItem($this->getTagKey($tag), $item->getKey()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Removes the key form all tag lists. When an item with tags is removed |
|||
* we MUST remove the tags. If we fail to remove the tags a new item with |
|||
* the same key will automatically get the previous tags. |
|||
* |
|||
* @param string $key |
|||
* |
|||
* @return $this |
|||
*/ |
|||
protected function preRemoveItem($key) |
|||
{ |
|||
$item = $this->getItem($key); |
|||
$this->removeTagEntries($item); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* @param PhpCacheItem $item |
|||
*/ |
|||
private function removeTagEntries(PhpCacheItem $item) |
|||
{ |
|||
$tags = $item->getPreviousTags(); |
|||
foreach ($tags as $tag) { |
|||
$this->removeListItem($this->getTagKey($tag), $item->getKey()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param string $tag |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected function getTagKey($tag) |
|||
{ |
|||
return 'tag'.self::SEPARATOR_TAG.$tag; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function get($key, $default = null) |
|||
{ |
|||
$item = $this->getItem($key); |
|||
if (!$item->isHit()) { |
|||
return $default; |
|||
} |
|||
|
|||
return $item->get(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function set($key, $value, $ttl = null) |
|||
{ |
|||
$item = $this->getItem($key); |
|||
$item->set($value); |
|||
$item->expiresAfter($ttl); |
|||
|
|||
return $this->save($item); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
return $this->deleteItem($key); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getMultiple($keys, $default = null) |
|||
{ |
|||
if (!is_array($keys)) { |
|||
if (!$keys instanceof \Traversable) { |
|||
throw new InvalidArgumentException('$keys is neither an array nor Traversable'); |
|||
} |
|||
|
|||
// Since we need to throw an exception if *any* key is invalid, it doesn't |
|||
// make sense to wrap iterators or something like that. |
|||
$keys = iterator_to_array($keys, false); |
|||
} |
|||
|
|||
$items = $this->getItems($keys); |
|||
|
|||
return $this->generateValues($default, $items); |
|||
} |
|||
|
|||
/** |
|||
* @param $default |
|||
* @param $items |
|||
* |
|||
* @return \Generator |
|||
*/ |
|||
private function generateValues($default, $items) |
|||
{ |
|||
foreach ($items as $key => $item) { |
|||
/** @type $item CacheItemInterface */ |
|||
if (!$item->isHit()) { |
|||
yield $key => $default; |
|||
} else { |
|||
yield $key => $item->get(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function setMultiple($values, $ttl = null) |
|||
{ |
|||
if (!is_array($values)) { |
|||
if (!$values instanceof \Traversable) { |
|||
throw new InvalidArgumentException('$values is neither an array nor Traversable'); |
|||
} |
|||
} |
|||
|
|||
$keys = []; |
|||
$arrayValues = []; |
|||
foreach ($values as $key => $value) { |
|||
if (is_int($key)) { |
|||
$key = (string) $key; |
|||
} |
|||
$this->validateKey($key); |
|||
$keys[] = $key; |
|||
$arrayValues[$key] = $value; |
|||
} |
|||
|
|||
$items = $this->getItems($keys); |
|||
$itemSuccess = true; |
|||
foreach ($items as $key => $item) { |
|||
$item->set($arrayValues[$key]); |
|||
|
|||
try { |
|||
$item->expiresAfter($ttl); |
|||
} catch (InvalidArgumentException $e) { |
|||
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); |
|||
} |
|||
|
|||
$itemSuccess = $itemSuccess && $this->saveDeferred($item); |
|||
} |
|||
|
|||
return $itemSuccess && $this->commit(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function deleteMultiple($keys) |
|||
{ |
|||
if (!is_array($keys)) { |
|||
if (!$keys instanceof \Traversable) { |
|||
throw new InvalidArgumentException('$keys is neither an array nor Traversable'); |
|||
} |
|||
|
|||
// Since we need to throw an exception if *any* key is invalid, it doesn't |
|||
// make sense to wrap iterators or something like that. |
|||
$keys = iterator_to_array($keys, false); |
|||
} |
|||
|
|||
return $this->deleteItems($keys); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function has($key) |
|||
{ |
|||
return $this->hasItem($key); |
|||
} |
|||
} |
@ -0,0 +1,269 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
use Cache\Adapter\Common\Exception\InvalidArgumentException; |
|||
use Cache\TagInterop\TaggableCacheItemInterface; |
|||
|
|||
/** |
|||
* @author Aaron Scherer <aequasi@gmail.com> |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
class CacheItem implements PhpCacheItem |
|||
{ |
|||
/** |
|||
* @type array |
|||
*/ |
|||
private $prevTags = []; |
|||
|
|||
/** |
|||
* @type array |
|||
*/ |
|||
private $tags = []; |
|||
|
|||
/** |
|||
* @type \Closure |
|||
*/ |
|||
private $callable; |
|||
|
|||
/** |
|||
* @type string |
|||
*/ |
|||
private $key; |
|||
|
|||
/** |
|||
* @type mixed |
|||
*/ |
|||
private $value; |
|||
|
|||
/** |
|||
* The expiration timestamp is the source of truth. This is the UTC timestamp |
|||
* when the cache item expire. A value of zero means it never expires. A nullvalue |
|||
* means that no expiration is set. |
|||
* |
|||
* @type int|null |
|||
*/ |
|||
private $expirationTimestamp = null; |
|||
|
|||
/** |
|||
* @type bool |
|||
*/ |
|||
private $hasValue = false; |
|||
|
|||
/** |
|||
* @param string $key |
|||
* @param \Closure|bool $callable or boolean hasValue |
|||
*/ |
|||
public function __construct($key, $callable = null, $value = null) |
|||
{ |
|||
$this->key = $key; |
|||
|
|||
if ($callable === true) { |
|||
$this->hasValue = true; |
|||
$this->value = $value; |
|||
} elseif ($callable !== false) { |
|||
// This must be a callable or null |
|||
$this->callable = $callable; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getKey() |
|||
{ |
|||
return $this->key; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function set($value) |
|||
{ |
|||
$this->value = $value; |
|||
$this->hasValue = true; |
|||
$this->callable = null; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function get() |
|||
{ |
|||
if (!$this->isHit()) { |
|||
return; |
|||
} |
|||
|
|||
return $this->value; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function isHit() |
|||
{ |
|||
$this->initialize(); |
|||
|
|||
if (!$this->hasValue) { |
|||
return false; |
|||
} |
|||
|
|||
if ($this->expirationTimestamp !== null) { |
|||
return $this->expirationTimestamp > time(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getExpirationTimestamp() |
|||
{ |
|||
return $this->expirationTimestamp; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function expiresAt($expiration) |
|||
{ |
|||
if ($expiration instanceof \DateTimeInterface) { |
|||
$this->expirationTimestamp = $expiration->getTimestamp(); |
|||
} elseif (is_int($expiration) || null === $expiration) { |
|||
$this->expirationTimestamp = $expiration; |
|||
} else { |
|||
throw new InvalidArgumentException('Cache item ttl/expiresAt must be of type integer or \DateTimeInterface.'); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function expiresAfter($time) |
|||
{ |
|||
if ($time === null) { |
|||
$this->expirationTimestamp = null; |
|||
} elseif ($time instanceof \DateInterval) { |
|||
$date = new \DateTime(); |
|||
$date->add($time); |
|||
$this->expirationTimestamp = $date->getTimestamp(); |
|||
} elseif (is_int($time)) { |
|||
$this->expirationTimestamp = time() + $time; |
|||
} else { |
|||
throw new InvalidArgumentException('Cache item ttl/expiresAfter must be of type integer or \DateInterval.'); |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getPreviousTags() |
|||
{ |
|||
$this->initialize(); |
|||
|
|||
return $this->prevTags; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getTags() |
|||
{ |
|||
return $this->tags; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function setTags(array $tags) |
|||
{ |
|||
$this->tags = []; |
|||
$this->tag($tags); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* Adds a tag to a cache item. |
|||
* |
|||
* @param string|string[] $tags A tag or array of tags |
|||
* |
|||
* @throws InvalidArgumentException When $tag is not valid. |
|||
* |
|||
* @return TaggableCacheItemInterface |
|||
*/ |
|||
private function tag($tags) |
|||
{ |
|||
$this->initialize(); |
|||
|
|||
if (!is_array($tags)) { |
|||
$tags = [$tags]; |
|||
} |
|||
foreach ($tags as $tag) { |
|||
if (!is_string($tag)) { |
|||
throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag))); |
|||
} |
|||
if (isset($this->tags[$tag])) { |
|||
continue; |
|||
} |
|||
if (!isset($tag[0])) { |
|||
throw new InvalidArgumentException('Cache tag length must be greater than zero'); |
|||
} |
|||
if (isset($tag[strcspn($tag, '{}()/\@:')])) { |
|||
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag)); |
|||
} |
|||
$this->tags[$tag] = $tag; |
|||
} |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* If callable is not null, execute it an populate this object with values. |
|||
*/ |
|||
private function initialize() |
|||
{ |
|||
if ($this->callable !== null) { |
|||
// $func will be $adapter->fetchObjectFromCache(); |
|||
$func = $this->callable; |
|||
$result = $func(); |
|||
$this->hasValue = $result[0]; |
|||
$this->value = $result[1]; |
|||
$this->prevTags = isset($result[2]) ? $result[2] : []; |
|||
$this->expirationTimestamp = null; |
|||
|
|||
if (isset($result[3]) && is_int($result[3])) { |
|||
$this->expirationTimestamp = $result[3]; |
|||
} |
|||
|
|||
$this->callable = null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @internal This function should never be used and considered private. |
|||
* |
|||
* Move tags from $tags to $prevTags |
|||
*/ |
|||
public function moveTagsToPrevious() |
|||
{ |
|||
$this->prevTags = $this->tags; |
|||
$this->tags = []; |
|||
} |
|||
} |
@ -0,0 +1,61 @@ |
|||
# Change Log |
|||
|
|||
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. |
|||
|
|||
## 1.1.0 |
|||
|
|||
### Added |
|||
|
|||
- Support for storing binary data |
|||
|
|||
### Fixed |
|||
|
|||
- Issue with one character variables |
|||
|
|||
### Changed |
|||
|
|||
- Tests are now extending `PHPUnit\Framework\TestCase` |
|||
|
|||
## 1.0.0 |
|||
|
|||
* No changes since 0.4.0. |
|||
|
|||
## 0.4.0 |
|||
|
|||
### Added |
|||
|
|||
* `AbstractCachePool` has 4 new abstract methods: `getList`, `removeList`, `appendListItem` and `removeListItem`. |
|||
* `AbstractCachePool::invalidateTags` and `AbstractCachePool::invalidateTags` |
|||
* Added interfaces for our items and pools `PhpCachePool` and `PhpCacheItem` |
|||
* Trait to help adapters to support tags. `TagSupportWithArray`. |
|||
|
|||
### Changed |
|||
|
|||
* First parameter to `AbstractCachePool::storeItemInCache` must be a `PhpCacheItem`. |
|||
* Return value from `AbstractCachePool::fetchObjectFromCache` must be a an array with 4 values. Added expiration timestamp. |
|||
* `HasExpirationDateInterface` is replaced by `HasExpirationTimestampInterface` |
|||
* We do not work with `\DateTime` internally anymore. We work with timestamps. |
|||
|
|||
## 0.3.3 |
|||
|
|||
### Fixed |
|||
|
|||
* Bugfix when you fetch data from the cache storage that was saved as "non-tagging item" but fetch as a tagging item. |
|||
|
|||
## 0.3.2 |
|||
|
|||
### Added |
|||
|
|||
* Cache pools do implement `LoggerAwareInterface` |
|||
|
|||
## 0.3.0 |
|||
|
|||
### Changed |
|||
|
|||
* The `AbstractCachePool` does not longer implement `TaggablePoolInterface`. However, the `CacheItem` does still implement `TaggableItemInterface`. |
|||
* `CacheItem::getKeyFromTaggedKey` has been removed |
|||
* The `CacheItem`'s second parameter is a callable that must return an array with 3 elements; [`hasValue`, `value`, `tags`]. |
|||
|
|||
## 0.2.0 |
|||
|
|||
* No changelog before this version |
@ -0,0 +1,23 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common\Exception; |
|||
|
|||
use Psr\Cache\CacheException as CacheExceptionInterface; |
|||
|
|||
/** |
|||
* A base exception. All exceptions in this organization will extend this exception. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
abstract class CacheException extends \RuntimeException implements CacheExceptionInterface |
|||
{ |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common\Exception; |
|||
|
|||
/** |
|||
* If an exception is caused by a pool or by the cache storage. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
class CachePoolException extends CacheException |
|||
{ |
|||
} |
@ -0,0 +1,19 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common\Exception; |
|||
|
|||
use Psr\Cache\InvalidArgumentException as CacheInvalidArgumentException; |
|||
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentException; |
|||
|
|||
class InvalidArgumentException extends CacheException implements CacheInvalidArgumentException, SimpleCacheInvalidArgumentException |
|||
{ |
|||
} |
@ -0,0 +1,26 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
/** |
|||
* @author Aaron Scherer <aequasi@gmail.com> |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
interface HasExpirationTimestampInterface |
|||
{ |
|||
/** |
|||
* The timestamp when the object expires. |
|||
* |
|||
* @return int|null |
|||
*/ |
|||
public function getExpirationTimestamp(); |
|||
} |
@ -0,0 +1,68 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
/** |
|||
* This trait provides common routines for safely encoding binary and non-UTF8 data in |
|||
* JSON. This is needed for components that use JSON natively (currently, the MongoDB |
|||
* adapter and EncryptedCachePool). |
|||
* |
|||
* @author Stephen Clouse <stephen.clouse@noaa.gov> |
|||
*/ |
|||
trait JsonBinaryArmoring |
|||
{ |
|||
private static $ESCAPE_JSON_CHARACTERS = [ |
|||
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", |
|||
"\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F", |
|||
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", |
|||
"\x18", "\x19", "\x1A", "\x1B", "\x1C", "\x1D", "\x1E", "\x1F", |
|||
]; |
|||
|
|||
private static $ENCODED_JSON_CHARACTERS = [ |
|||
'\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', |
|||
'\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', |
|||
'\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', |
|||
'\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', |
|||
]; |
|||
|
|||
/** |
|||
* Armor a value going into a JSON document. |
|||
* |
|||
* @param string $value |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected static function jsonArmor($value) |
|||
{ |
|||
return str_replace( |
|||
static::$ESCAPE_JSON_CHARACTERS, |
|||
static::$ENCODED_JSON_CHARACTERS, |
|||
utf8_encode($value) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* De-armor a value from a JSON document. |
|||
* |
|||
* @param string $value |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected static function jsonDeArmor($value) |
|||
{ |
|||
return utf8_decode(str_replace( |
|||
static::$ENCODED_JSON_CHARACTERS, |
|||
static::$ESCAPE_JSON_CHARACTERS, |
|||
$value |
|||
)); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Aaron Scherer, Tobias Nyholm |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
use Cache\TagInterop\TaggableCacheItemInterface; |
|||
|
|||
/** |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
interface PhpCacheItem extends HasExpirationTimestampInterface, TaggableCacheItemInterface |
|||
{ |
|||
/** |
|||
* Get the current tags. These are not the same tags as getPrevious tags. This |
|||
* is the tags that has been added to the item after the item was fetched from |
|||
* the cache storage. |
|||
* |
|||
* WARNING: This is generally not the function you want to use. Please see |
|||
* `getPreviousTags`. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getTags(); |
|||
} |
@ -0,0 +1,34 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
use Cache\TagInterop\TaggableCacheItemPoolInterface; |
|||
|
|||
/** |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
interface PhpCachePool extends TaggableCacheItemPoolInterface |
|||
{ |
|||
/** |
|||
* {@inheritdoc} |
|||
* |
|||
* @return PhpCacheItem |
|||
*/ |
|||
public function getItem($key); |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
* |
|||
* @return array|\Traversable|PhpCacheItem[] |
|||
*/ |
|||
public function getItems(array $keys = []); |
|||
} |
@ -0,0 +1,15 @@ |
|||
# Common PSR-6 Cache pool |
|||
[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
|||
[![Latest Stable Version](https://poser.pugx.org/cache/adapter-common/v/stable)](https://packagist.org/packages/cache/adapter-common) |
|||
[![codecov.io](https://codecov.io/github/php-cache/adapter-common/coverage.svg?branch=master)](https://codecov.io/github/php-cache/adapter-common?branch=master) |
|||
[![Total Downloads](https://poser.pugx.org/cache/adapter-common/downloads)](https://packagist.org/packages/cache/adapter-common) |
|||
[![Monthly Downloads](https://poser.pugx.org/cache/adapter-common/d/monthly.png)](https://packagist.org/packages/cache/adapter-common) |
|||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) |
|||
|
|||
This repository contains shared classes and interfaces used by the PHP Cache organisation. To read about |
|||
features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com). |
|||
|
|||
### Contribute |
|||
|
|||
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or |
|||
report any issues you find on the [issue tracker](http://issues.php-cache.com). |
@ -0,0 +1,88 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common; |
|||
|
|||
/** |
|||
* This trait could be used by adapters that do not have a native support for lists. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
trait TagSupportWithArray |
|||
{ |
|||
/** |
|||
* Get a value from the storage. |
|||
* |
|||
* @param string $name |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
abstract public function getDirectValue($name); |
|||
|
|||
/** |
|||
* Set a value to the storage. |
|||
* |
|||
* @param string $name |
|||
* @param mixed $value |
|||
*/ |
|||
abstract public function setDirectValue($name, $value); |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function appendListItem($name, $value) |
|||
{ |
|||
$data = $this->getDirectValue($name); |
|||
if (!is_array($data)) { |
|||
$data = []; |
|||
} |
|||
$data[] = $value; |
|||
$this->setDirectValue($name, $data); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function getList($name) |
|||
{ |
|||
$data = $this->getDirectValue($name); |
|||
if (!is_array($data)) { |
|||
$data = []; |
|||
} |
|||
|
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function removeList($name) |
|||
{ |
|||
$this->setDirectValue($name, []); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function removeListItem($name, $key) |
|||
{ |
|||
$data = $this->getList($name); |
|||
foreach ($data as $i => $value) { |
|||
if ($key === $value) { |
|||
unset($data[$i]); |
|||
} |
|||
} |
|||
|
|||
return $this->setDirectValue($name, $data); |
|||
} |
|||
} |
@ -0,0 +1,126 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Common\Tests; |
|||
|
|||
use Cache\Adapter\Common\CacheItem; |
|||
use PHPUnit\Framework\TestCase; |
|||
use Psr\Cache\CacheItemInterface; |
|||
|
|||
class CacheItemTest extends TestCase |
|||
{ |
|||
public function testConstructor() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
|
|||
$this->assertInstanceOf(CacheItem::class, $item); |
|||
$this->assertInstanceOf(CacheItemInterface::class, $item); |
|||
} |
|||
|
|||
public function testGetKey() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
$this->assertEquals('test_key', $item->getKey()); |
|||
} |
|||
|
|||
public function testSet() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
|
|||
$ref = new \ReflectionObject($item); |
|||
$valueProp = $ref->getProperty('value'); |
|||
$valueProp->setAccessible(true); |
|||
$hasValueProp = $ref->getProperty('hasValue'); |
|||
$hasValueProp->setAccessible(true); |
|||
|
|||
$this->assertEquals(null, $valueProp->getValue($item)); |
|||
$this->assertFalse($hasValueProp->getValue($item)); |
|||
|
|||
$item->set('value'); |
|||
|
|||
$this->assertEquals('value', $valueProp->getValue($item)); |
|||
$this->assertTrue($hasValueProp->getValue($item)); |
|||
} |
|||
|
|||
public function testGet() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
$this->assertNull($item->get()); |
|||
|
|||
$item->set('test'); |
|||
$this->assertEquals('test', $item->get()); |
|||
} |
|||
|
|||
public function testHit() |
|||
{ |
|||
$item = new CacheItem('test_key', true, 'value'); |
|||
$this->assertTrue($item->isHit()); |
|||
|
|||
$item = new CacheItem('test_key', false, 'value'); |
|||
$this->assertFalse($item->isHit()); |
|||
|
|||
$closure = function () { |
|||
return [true, 'value', []]; |
|||
}; |
|||
$item = new CacheItem('test_key', $closure); |
|||
$this->assertTrue($item->isHit()); |
|||
|
|||
$closure = function () { |
|||
return [false, null, []]; |
|||
}; |
|||
$item = new CacheItem('test_key', $closure); |
|||
$this->assertFalse($item->isHit()); |
|||
} |
|||
|
|||
public function testGetExpirationTimestamp() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
|
|||
$this->assertNull($item->getExpirationTimestamp()); |
|||
|
|||
$timestamp = time(); |
|||
|
|||
$ref = new \ReflectionObject($item); |
|||
$prop = $ref->getProperty('expirationTimestamp'); |
|||
$prop->setAccessible(true); |
|||
$prop->setValue($item, $timestamp); |
|||
|
|||
$this->assertEquals($timestamp, $item->getExpirationTimestamp()); |
|||
} |
|||
|
|||
public function testExpiresAt() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
|
|||
$this->assertNull($item->getExpirationTimestamp()); |
|||
|
|||
$time = time() + 1; |
|||
$item->expiresAt($time); |
|||
|
|||
$this->assertEquals($time, $item->getExpirationTimestamp()); |
|||
} |
|||
|
|||
public function testExpiresAfter() |
|||
{ |
|||
$item = new CacheItem('test_key'); |
|||
|
|||
$this->assertNull($item->getExpirationTimestamp()); |
|||
|
|||
$item->expiresAfter(null); |
|||
$this->assertNull($this->getExpectedException()); |
|||
|
|||
$item->expiresAfter(new \DateInterval('PT1S')); |
|||
$this->assertEquals((new \DateTime('+1 second'))->getTimestamp(), $item->getExpirationTimestamp()); |
|||
|
|||
$item->expiresAfter(1); |
|||
$this->assertEquals((new \DateTime('+1 second'))->getTimestamp(), $item->getExpirationTimestamp()); |
|||
} |
|||
} |
@ -0,0 +1,55 @@ |
|||
{ |
|||
"name": "cache/adapter-common", |
|||
"description": "Common classes for PSR-6 adapters", |
|||
"type": "library", |
|||
"license": "MIT", |
|||
"minimum-stability": "dev", |
|||
"prefer-stable": true, |
|||
"keywords": [ |
|||
"cache", |
|||
"psr-6", |
|||
"tag" |
|||
], |
|||
"homepage": "http://www.php-cache.com/en/latest/", |
|||
"authors": [ |
|||
{ |
|||
"name": "Aaron Scherer", |
|||
"email": "aequasi@gmail.com", |
|||
"homepage": "https://github.com/aequasi" |
|||
}, |
|||
{ |
|||
"name": "Tobias Nyholm", |
|||
"email": "tobias.nyholm@gmail.com", |
|||
"homepage": "https://github.com/nyholm" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": "^5.6 || ^7.0", |
|||
"psr/cache": "^1.0", |
|||
"psr/simple-cache": "^1.0", |
|||
"psr/log": "^1.0", |
|||
"cache/tag-interop": "^1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "^5.7.21", |
|||
"cache/integration-tests": "^0.16" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Cache\\Adapter\\Common\\": "" |
|||
} |
|||
}, |
|||
"autoload-dev": { |
|||
"psr-4": { |
|||
"Cache\\Adapter\\Common\\Tests\\": "Tests/" |
|||
}, |
|||
"exclude-from-classmap": [ |
|||
"/Tests/" |
|||
] |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.1-dev" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<phpunit backupGlobals="false" |
|||
backupStaticAttributes="false" |
|||
colors="true" |
|||
convertErrorsToExceptions="true" |
|||
convertNoticesToExceptions="true" |
|||
convertWarningsToExceptions="true" |
|||
processIsolation="false" |
|||
stopOnFailure="false" |
|||
syntaxCheck="false" |
|||
bootstrap="vendor/autoload.php" |
|||
> |
|||
<testsuites> |
|||
<testsuite name="Main Test Suite"> |
|||
<directory>./Tests/</directory> |
|||
</testsuite> |
|||
</testsuites> |
|||
|
|||
<groups> |
|||
<exclude> |
|||
<group>benchmark</group> |
|||
</exclude> |
|||
</groups> |
|||
|
|||
<filter> |
|||
<whitelist> |
|||
<directory>./</directory> |
|||
<exclude> |
|||
<directory>./Tests</directory> |
|||
<directory>./vendor</directory> |
|||
</exclude> |
|||
</whitelist> |
|||
</filter> |
|||
</phpunit> |
@ -0,0 +1,22 @@ |
|||
language: php |
|||
sudo: false |
|||
|
|||
matrix: |
|||
include: |
|||
- php: 7.0 |
|||
|
|||
cache: |
|||
directories: |
|||
- "$HOME/.composer/cache" |
|||
|
|||
install: |
|||
- composer update --prefer-dist --prefer-stable |
|||
|
|||
script: |
|||
- ./vendor/bin/phpunit --coverage-clover=coverage.xml |
|||
|
|||
after_success: |
|||
- pip install --user codecov && codecov |
|||
|
|||
notifications: |
|||
email: false |
@ -0,0 +1,29 @@ |
|||
# Change Log |
|||
|
|||
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. |
|||
|
|||
## UNRELEASED |
|||
|
|||
## 1.0.0 |
|||
|
|||
* No changes since 0.4.0 |
|||
|
|||
## 0.4.0 |
|||
|
|||
### Changed |
|||
|
|||
* `HierarchicalCachePoolTrait::getValueFormStore` was renamed to `HierarchicalCachePoolTrait::getDirectValue` |
|||
|
|||
### Removed |
|||
|
|||
* Dependency to `cache/taggable-cache`. |
|||
|
|||
## 0.3.0 |
|||
|
|||
### Changed |
|||
|
|||
* The `HierarchicalPoolInterface` extends `CacheItemPoolInterface` |
|||
|
|||
## 0.2.1 |
|||
|
|||
* No changelog before this version |
@ -0,0 +1,125 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Hierarchy; |
|||
|
|||
use Cache\Adapter\Common\AbstractCachePool; |
|||
|
|||
/** |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
trait HierarchicalCachePoolTrait |
|||
{ |
|||
/** |
|||
* A temporary cache for keys. |
|||
* |
|||
* @type array |
|||
*/ |
|||
private $keyCache = []; |
|||
|
|||
/** |
|||
* Get a value from the storage. |
|||
* |
|||
* @param string $name |
|||
* |
|||
* @return mixed |
|||
*/ |
|||
abstract public function getDirectValue($name); |
|||
|
|||
/** |
|||
* Get a key to use with the hierarchy. If the key does not start with HierarchicalPoolInterface::SEPARATOR |
|||
* this will return an unalterered key. This function supports a tagged key. Ie "foo:bar". |
|||
* |
|||
* @param string $key The original key |
|||
* @param string &$pathKey A cache key for the path. If this key is changed everything beyond that path is changed. |
|||
* |
|||
* @return string|array |
|||
*/ |
|||
protected function getHierarchyKey($key, &$pathKey = null) |
|||
{ |
|||
if (!$this->isHierarchyKey($key)) { |
|||
return $key; |
|||
} |
|||
|
|||
$key = $this->explodeKey($key); |
|||
|
|||
$keyString = ''; |
|||
// The comments below is for a $key = ["foo!tagHash", "bar!tagHash"] |
|||
foreach ($key as $name) { |
|||
// 1) $keyString = "foo!tagHash" |
|||
// 2) $keyString = "foo!tagHash![foo_index]!bar!tagHash" |
|||
$keyString .= $name; |
|||
$pathKey = sha1('path'.AbstractCachePool::SEPARATOR_TAG.$keyString); |
|||
|
|||
if (isset($this->keyCache[$pathKey])) { |
|||
$index = $this->keyCache[$pathKey]; |
|||
} else { |
|||
$index = $this->getDirectValue($pathKey); |
|||
$this->keyCache[$pathKey] = $index; |
|||
} |
|||
|
|||
// 1) $keyString = "foo!tagHash![foo_index]!" |
|||
// 2) $keyString = "foo!tagHash![foo_index]!bar!tagHash![bar_index]!" |
|||
$keyString .= AbstractCachePool::SEPARATOR_TAG.$index.AbstractCachePool::SEPARATOR_TAG; |
|||
} |
|||
|
|||
// Assert: $pathKey = "path!foo!tagHash![foo_index]!bar!tagHash" |
|||
// Assert: $keyString = "foo!tagHash![foo_index]!bar!tagHash![bar_index]!" |
|||
|
|||
// Make sure we do not get awfully long (>250 chars) keys |
|||
return sha1($keyString); |
|||
} |
|||
|
|||
/** |
|||
* Clear the cache for the keys. |
|||
*/ |
|||
protected function clearHierarchyKeyCache() |
|||
{ |
|||
$this->keyCache = []; |
|||
} |
|||
|
|||
/** |
|||
* A hierarchy key MUST begin with the separator. |
|||
* |
|||
* @param string $key |
|||
* |
|||
* @return bool |
|||
*/ |
|||
private function isHierarchyKey($key) |
|||
{ |
|||
return substr($key, 0, 1) === HierarchicalPoolInterface::HIERARCHY_SEPARATOR; |
|||
} |
|||
|
|||
/** |
|||
* This will take a hierarchy key ("|foo|bar") with tags ("|foo|bar!tagHash") and return an array with |
|||
* each level in the hierarchy appended with the tags. ["foo!tagHash", "bar!tagHash"]. |
|||
* |
|||
* @param string $string |
|||
* |
|||
* @return array |
|||
*/ |
|||
private function explodeKey($string) |
|||
{ |
|||
list($key, $tag) = explode(AbstractCachePool::SEPARATOR_TAG, $string.AbstractCachePool::SEPARATOR_TAG); |
|||
|
|||
if ($key === HierarchicalPoolInterface::HIERARCHY_SEPARATOR) { |
|||
$parts = ['root']; |
|||
} else { |
|||
$parts = explode(HierarchicalPoolInterface::HIERARCHY_SEPARATOR, $key); |
|||
// remove first element since it is always empty and replace it with 'root' |
|||
$parts[0] = 'root'; |
|||
} |
|||
|
|||
return array_map(function ($level) use ($tag) { |
|||
return $level.AbstractCachePool::SEPARATOR_TAG.$tag; |
|||
}, $parts); |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Hierarchy; |
|||
|
|||
use Psr\Cache\CacheItemPoolInterface; |
|||
|
|||
/** |
|||
* Let you use hierarchy if you start your tag key with the HIERARCHY_SEPARATOR. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
interface HierarchicalPoolInterface extends CacheItemPoolInterface |
|||
{ |
|||
const HIERARCHY_SEPARATOR = '|'; |
|||
} |
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Aaron Scherer, Tobias Nyholm |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
@ -0,0 +1,35 @@ |
|||
# Hierarchical PSR-6 cache pool |
|||
[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
|||
[![Latest Stable Version](https://poser.pugx.org/cache/hierarchical-cache/v/stable)](https://packagist.org/packages/cache/hierarchical-cache) |
|||
[![codecov.io](https://codecov.io/github/php-cache/hierarchical-cache/coverage.svg?branch=master)](https://codecov.io/github/php-cache/hierarchical-cache?branch=master) |
|||
[![Total Downloads](https://poser.pugx.org/cache/hierarchical-cache/downloads)](https://packagist.org/packages/cache/hierarchical-cache) |
|||
[![Monthly Downloads](https://poser.pugx.org/cache/hierarchical-cache/d/monthly.png)](https://packagist.org/packages/cache/hierarchical-cache) |
|||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) |
|||
|
|||
This is an implementation for the PSR-6 for an hierarchical cache architecture. |
|||
|
|||
If you have a cache key like `|users|:uid|followers|:fid|likes` where `:uid` and `:fid` are arbitrary integers. You |
|||
may flush all followers by flushing `|users|:uid|followers`. |
|||
|
|||
It is a part of the PHP Cache organisation. To read about features like tagging and hierarchy support please read |
|||
the shared documentation at [www.php-cache.com](http://www.php-cache.com). |
|||
|
|||
### Install |
|||
|
|||
```bash |
|||
composer require cache/hierarchical-cache |
|||
``` |
|||
|
|||
### Use |
|||
|
|||
Read the [documentation on usage](http://www.php-cache.com/en/latest/hierarchy/). |
|||
|
|||
### Implement |
|||
|
|||
Read the [documentation on implementation](http://www.php-cache.com/en/latest/implementing-cache-pools/hierarchy/). |
|||
|
|||
### Contribute |
|||
|
|||
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or |
|||
report any issues you find on the [issue tracker](http://issues.php-cache.com). |
|||
|
@ -0,0 +1,49 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Hierarchy\Tests\Helper; |
|||
|
|||
use Cache\Hierarchy\HierarchicalCachePoolTrait; |
|||
|
|||
/** |
|||
* A cache pool used in tests. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
class CachePool |
|||
{ |
|||
use HierarchicalCachePoolTrait; |
|||
|
|||
private $storeValues = []; |
|||
|
|||
/** |
|||
* @param array $storeValues |
|||
*/ |
|||
public function __construct(array $storeValues = []) |
|||
{ |
|||
$this->storeValues = $storeValues; |
|||
} |
|||
|
|||
public function exposeClearHierarchyKeyCache() |
|||
{ |
|||
$this->clearHierarchyKeyCache(); |
|||
} |
|||
|
|||
public function exposeGetHierarchyKey($key, &$pathKey = null) |
|||
{ |
|||
return $this->getHierarchyKey($key, $pathKey); |
|||
} |
|||
|
|||
protected function getDirectValue($key) |
|||
{ |
|||
return array_shift($this->storeValues); |
|||
} |
|||
} |
@ -0,0 +1,163 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Hierarchy\Tests; |
|||
|
|||
use Cache\Hierarchy\Tests\Helper\CachePool; |
|||
use PHPUnit\Framework\TestCase; |
|||
|
|||
/** |
|||
* We should not use constants on interfaces in the tests. Tests should break if the constant is changed. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
class HierarchicalCachePoolTest extends TestCase |
|||
{ |
|||
private function assertEqualsSha1($expected, $result, $message = '') |
|||
{ |
|||
$this->assertEquals(sha1($expected), $result, $message); |
|||
} |
|||
|
|||
public function testGetHierarchyKey() |
|||
{ |
|||
$path = null; |
|||
|
|||
$pool = new CachePool(); |
|||
$result = $pool->exposeGetHierarchyKey('key', $path); |
|||
$this->assertEquals('key', $result); |
|||
$this->assertNull($path); |
|||
|
|||
$pool = new CachePool(['idx_1', 'idx_2', 'idx_3']); |
|||
$result = $pool->exposeGetHierarchyKey('|foo|bar', $path); |
|||
$this->assertEqualsSha1('root!!idx_1!foo!!idx_2!bar!!idx_3!', $result); |
|||
$this->assertEqualsSha1('path!root!!idx_1!foo!!idx_2!bar!', $path); |
|||
|
|||
$pool = new CachePool(['idx_1', 'idx_2', 'idx_3']); |
|||
$result = $pool->exposeGetHierarchyKey('|', $path); |
|||
$this->assertEqualsSha1('path!root!', $path); |
|||
$this->assertEqualsSha1('root!!idx_1!', $result); |
|||
} |
|||
|
|||
public function testGetHierarchyKeyWithTags() |
|||
{ |
|||
$path = null; |
|||
|
|||
$pool = new CachePool(); |
|||
$result = $pool->exposeGetHierarchyKey('key!tagHash', $path); |
|||
$this->assertEquals('key!tagHash', $result); |
|||
$this->assertNull($path); |
|||
|
|||
$pool = new CachePool(['idx_1', 'idx_2', 'idx_3']); |
|||
$result = $pool->exposeGetHierarchyKey('|foo|bar!tagHash', $path); |
|||
$this->assertEqualsSha1('root!tagHash!idx_1!foo!tagHash!idx_2!bar!tagHash!idx_3!', $result); |
|||
$this->assertEqualsSha1('path!root!tagHash!idx_1!foo!tagHash!idx_2!bar!tagHash', $path); |
|||
|
|||
$pool = new CachePool(['idx_1', 'idx_2', 'idx_3']); |
|||
$result = $pool->exposeGetHierarchyKey('|!tagHash', $path); |
|||
$this->assertEqualsSha1('path!root!tagHash', $path); |
|||
$this->assertEqualsSha1('root!tagHash!idx_1!', $result); |
|||
} |
|||
|
|||
public function testGetHierarchyKeyEmptyCache() |
|||
{ |
|||
$pool = new CachePool(); |
|||
$path = null; |
|||
|
|||
$result = $pool->exposeGetHierarchyKey('key', $path); |
|||
$this->assertEquals('key', $result); |
|||
$this->assertNull($path); |
|||
|
|||
$result = $pool->exposeGetHierarchyKey('|foo|bar', $path); |
|||
$this->assertEqualsSha1('root!!!foo!!!bar!!!', $result); |
|||
$this->assertEqualsSha1('path!root!!!foo!!!bar!', $path); |
|||
|
|||
$result = $pool->exposeGetHierarchyKey('|', $path); |
|||
$this->assertEqualsSha1('path!root!', $path); |
|||
$this->assertEqualsSha1('root!!!', $result); |
|||
} |
|||
|
|||
public function testKeyCache() |
|||
{ |
|||
$path = null; |
|||
|
|||
$pool = new CachePool(['idx_1', 'idx_2', 'idx_3']); |
|||
$result = $pool->exposeGetHierarchyKey('|foo', $path); |
|||
$this->assertEqualsSha1('root!!idx_1!foo!!idx_2!', $result); |
|||
$this->assertEqualsSha1('path!root!!idx_1!foo!', $path); |
|||
|
|||
// Make sure re reuse the old index value we already looked up for 'root'. |
|||
$result = $pool->exposeGetHierarchyKey('|bar', $path); |
|||
$this->assertEqualsSha1('root!!idx_1!bar!!idx_3!', $result); |
|||
$this->assertEqualsSha1('path!root!!idx_1!bar!', $path); |
|||
} |
|||
|
|||
public function testClearHierarchyKeyCache() |
|||
{ |
|||
$pool = new CachePool(); |
|||
$prop = new \ReflectionProperty('Cache\Hierarchy\Tests\Helper\CachePool', 'keyCache'); |
|||
$prop->setAccessible(true); |
|||
|
|||
// add some values to the prop and make sure they are beeing cleared |
|||
$prop->setValue($pool, ['foo' => 'bar', 'baz' => 'biz']); |
|||
$pool->exposeClearHierarchyKeyCache(); |
|||
$this->assertEmpty($prop->getValue($pool), 'The key cache must be cleared after ::ClearHierarchyKeyCache'); |
|||
} |
|||
|
|||
public function testIsHierarchyKey() |
|||
{ |
|||
$pool = new CachePool(); |
|||
$method = new \ReflectionMethod('Cache\Hierarchy\Tests\Helper\CachePool', 'isHierarchyKey'); |
|||
$method->setAccessible(true); |
|||
|
|||
$this->assertFalse($method->invoke($pool, 'key')); |
|||
$this->assertFalse($method->invoke($pool, 'key|bar')); |
|||
$this->assertFalse($method->invoke($pool, 'key|')); |
|||
$this->assertTrue($method->invoke($pool, '|key')); |
|||
$this->assertTrue($method->invoke($pool, '|key|bar')); |
|||
} |
|||
|
|||
public function testExplodeKey() |
|||
{ |
|||
$pool = new CachePool(); |
|||
$method = new \ReflectionMethod('Cache\Hierarchy\Tests\Helper\CachePool', 'explodeKey'); |
|||
$method->setAccessible(true); |
|||
|
|||
$result = $method->invoke($pool, '|key'); |
|||
$this->assertCount(2, $result); |
|||
$this->assertEquals('key!', $result[1]); |
|||
$this->assertTrue(in_array('key!', $result)); |
|||
|
|||
$result = $method->invoke($pool, '|key|bar'); |
|||
$this->assertCount(3, $result); |
|||
$this->assertTrue(in_array('key!', $result)); |
|||
$this->assertTrue(in_array('bar!', $result)); |
|||
|
|||
$result = $method->invoke($pool, '|'); |
|||
$this->assertCount(1, $result); |
|||
} |
|||
|
|||
public function testExplodeKeyWithTags() |
|||
{ |
|||
$pool = new CachePool(); |
|||
$method = new \ReflectionMethod('Cache\Hierarchy\Tests\Helper\CachePool', 'explodeKey'); |
|||
$method->setAccessible(true); |
|||
|
|||
$result = $method->invoke($pool, '|key|bar!hash'); |
|||
$this->assertCount(3, $result); |
|||
foreach ($result as $r) { |
|||
$this->assertRegExp('|.*!hash|s', $r, 'Tag hash must be on every level in hierarchy key'); |
|||
} |
|||
|
|||
$result = $method->invoke($pool, '|!hash'); |
|||
$this->assertCount(1, $result); |
|||
$this->assertRegExp('|.*!hash|s', $result[0], 'Tag hash must on root level in hierarchy key'); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
{ |
|||
"name": "cache/hierarchical-cache", |
|||
"description": "A helper trait and interface to your PSR-6 cache to support hierarchical keys.", |
|||
"type": "library", |
|||
"license": "MIT", |
|||
"keywords": [ |
|||
"cache", |
|||
"psr-6", |
|||
"hierarchy", |
|||
"hierarchical" |
|||
], |
|||
"homepage": "http://www.php-cache.com/en/latest/", |
|||
"authors": [ |
|||
{ |
|||
"name": "Aaron Scherer", |
|||
"email": "aequasi@gmail.com", |
|||
"homepage": "https://github.com/aequasi" |
|||
}, |
|||
{ |
|||
"name": "Tobias Nyholm", |
|||
"email": "tobias.nyholm@gmail.com", |
|||
"homepage": "https://github.com/nyholm" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": "^5.6 || ^7.0", |
|||
"psr/cache": "^1.0", |
|||
"cache/adapter-common": "^1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "^5.7.21" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Cache\\Hierarchy\\": "" |
|||
}, |
|||
"exclude-from-classmap": [ |
|||
"/Tests/" |
|||
] |
|||
}, |
|||
"minimum-stability": "dev", |
|||
"prefer-stable": true, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.1-dev" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<phpunit backupGlobals="false" |
|||
backupStaticAttributes="false" |
|||
colors="true" |
|||
convertErrorsToExceptions="true" |
|||
convertNoticesToExceptions="true" |
|||
convertWarningsToExceptions="true" |
|||
processIsolation="false" |
|||
stopOnFailure="false" |
|||
syntaxCheck="false" |
|||
bootstrap="vendor/autoload.php" |
|||
> |
|||
<testsuites> |
|||
<testsuite name="Main Test Suite"> |
|||
<directory>./Tests/</directory> |
|||
</testsuite> |
|||
</testsuites> |
|||
|
|||
<groups> |
|||
<exclude> |
|||
<group>benchmark</group> |
|||
</exclude> |
|||
</groups> |
|||
|
|||
<filter> |
|||
<whitelist> |
|||
<directory>./</directory> |
|||
<exclude> |
|||
<directory>./Tests</directory> |
|||
<directory>./vendor</directory> |
|||
</exclude> |
|||
</whitelist> |
|||
</filter> |
|||
</phpunit> |
@ -0,0 +1,22 @@ |
|||
language: php |
|||
sudo: false |
|||
|
|||
matrix: |
|||
include: |
|||
- php: 7.0 |
|||
|
|||
cache: |
|||
directories: |
|||
- "$HOME/.composer/cache" |
|||
|
|||
install: |
|||
- composer update --prefer-dist --prefer-stable |
|||
|
|||
script: |
|||
- ./vendor/bin/phpunit --coverage-clover=coverage.xml |
|||
|
|||
after_success: |
|||
- pip install --user codecov && codecov |
|||
|
|||
notifications: |
|||
email: false |
@ -0,0 +1,9 @@ |
|||
# Change Log |
|||
|
|||
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. |
|||
|
|||
## 1.0.0 |
|||
|
|||
* First release |
|||
|
|||
|
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Aaron Scherer, Tobias Nyholm |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
@ -0,0 +1,25 @@ |
|||
# Tag support for PSR-6 Cache |
|||
[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
|||
[![Latest Stable Version](https://poser.pugx.org/cache/tag-interop/v/stable)](https://packagist.org/packages/cache/tag-interop) |
|||
[![Total Downloads](https://poser.pugx.org/cache/tag-interop/downloads)](https://packagist.org/packages/cache/tag-interop) |
|||
[![Monthly Downloads](https://poser.pugx.org/cache/tag-interop/d/monthly.png)](https://packagist.org/packages/cache/tag-interop) |
|||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) |
|||
|
|||
This repository holds two interfaces for tagging. These interfaces will make their |
|||
way into PHP Fig. Representatives from Symfony, PHP-cache and Drupal has worked |
|||
together to agree on these interfaces. |
|||
|
|||
### Install |
|||
|
|||
```bash |
|||
composer require cache/tag-interop |
|||
``` |
|||
|
|||
### Use |
|||
|
|||
Read the [documentation on usage](http://www.php-cache.com/). |
|||
|
|||
### Contribute |
|||
|
|||
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or |
|||
report any issues you find on the [issue tracker](http://issues.php-cache.com). |
@ -0,0 +1,43 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\TagInterop; |
|||
|
|||
use Psr\Cache\CacheItemInterface; |
|||
use Psr\Cache\InvalidArgumentException; |
|||
|
|||
/** |
|||
* An item that supports tags. This interface is a soon-to-be-PSR. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* @author Nicolas Grekas <p@tchwork.com> |
|||
*/ |
|||
interface TaggableCacheItemInterface extends CacheItemInterface |
|||
{ |
|||
/** |
|||
* Get all existing tags. These are the tags the item has when the item is |
|||
* returned from the pool. |
|||
* |
|||
* @return array |
|||
*/ |
|||
public function getPreviousTags(); |
|||
|
|||
/** |
|||
* Overwrite all tags with a new set of tags. |
|||
* |
|||
* @param string[] $tags An array of tags |
|||
* |
|||
* @throws InvalidArgumentException When a tag is not valid. |
|||
* |
|||
* @return TaggableCacheItemInterface |
|||
*/ |
|||
public function setTags(array $tags); |
|||
} |
@ -0,0 +1,60 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\TagInterop; |
|||
|
|||
use Psr\Cache\CacheItemPoolInterface; |
|||
use Psr\Cache\InvalidArgumentException; |
|||
|
|||
/** |
|||
* Interface for invalidating cached items using tags. This interface is a soon-to-be-PSR. |
|||
* |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* @author Nicolas Grekas <p@tchwork.com> |
|||
*/ |
|||
interface TaggableCacheItemPoolInterface extends CacheItemPoolInterface |
|||
{ |
|||
/** |
|||
* Invalidates cached items using a tag. |
|||
* |
|||
* @param string $tag The tag to invalidate |
|||
* |
|||
* @throws InvalidArgumentException When $tags is not valid |
|||
* |
|||
* @return bool True on success |
|||
*/ |
|||
public function invalidateTag($tag); |
|||
|
|||
/** |
|||
* Invalidates cached items using tags. |
|||
* |
|||
* @param string[] $tags An array of tags to invalidate |
|||
* |
|||
* @throws InvalidArgumentException When $tags is not valid |
|||
* |
|||
* @return bool True on success |
|||
*/ |
|||
public function invalidateTags(array $tags); |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
* |
|||
* @return TaggableCacheItemInterface |
|||
*/ |
|||
public function getItem($key); |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
* |
|||
* @return array|\Traversable|TaggableCacheItemInterface[] |
|||
*/ |
|||
public function getItems(array $keys = []); |
|||
} |
@ -0,0 +1,39 @@ |
|||
{ |
|||
"name": "cache/tag-interop", |
|||
"type": "library", |
|||
"description": "Framework interoperable interfaces for tags", |
|||
"keywords": [ |
|||
"cache", |
|||
"psr6", |
|||
"tag", |
|||
"psr" |
|||
], |
|||
"homepage": "http://www.php-cache.com/en/latest/", |
|||
"license": "MIT", |
|||
"authors": [ |
|||
{ |
|||
"name": "Tobias Nyholm", |
|||
"email": "tobias.nyholm@gmail.com", |
|||
"homepage": "https://github.com/nyholm" |
|||
}, |
|||
{ |
|||
"name": "Nicolas Grekas ", |
|||
"email": "p@tchwork.com", |
|||
"homepage": "https://github.com/nicolas-grekas" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": "^5.5 || ^7.0", |
|||
"psr/cache": "^1.0" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Cache\\TagInterop\\": "" |
|||
} |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.1-dev" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
language: php |
|||
sudo: false |
|||
|
|||
matrix: |
|||
include: |
|||
- php: 7.0 |
|||
|
|||
cache: |
|||
directories: |
|||
- "$HOME/.composer/cache" |
|||
|
|||
install: |
|||
- composer update --prefer-dist --prefer-stable |
|||
|
|||
script: |
|||
- ./vendor/bin/phpunit --coverage-clover=coverage.xml |
|||
|
|||
after_success: |
|||
- pip install --user codecov && codecov |
|||
|
|||
notifications: |
|||
email: false |
@ -0,0 +1,42 @@ |
|||
# Change Log |
|||
|
|||
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. |
|||
|
|||
## UNRELEASED |
|||
|
|||
## 1.0.0 |
|||
|
|||
* No changes since 0.4.1. |
|||
|
|||
## 0.4.1 |
|||
|
|||
### Changed |
|||
|
|||
* We now support cache/hierarchical-cache: ^0.4 |
|||
|
|||
## 0.4.0 |
|||
|
|||
### Added |
|||
|
|||
* Support for the new `TaggableCacheItemPoolInterface`. |
|||
* Support for PSR-16 SimpleCache |
|||
|
|||
### Changed |
|||
|
|||
* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage. |
|||
|
|||
### Removed |
|||
|
|||
* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()` |
|||
* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()` |
|||
* `CacheItem::addTag()`. Use `CacheItem::setTags()` |
|||
|
|||
## 0.3.1 |
|||
|
|||
### Changed |
|||
|
|||
* Updated dependencies |
|||
|
|||
## 0.3.0 |
|||
|
|||
* No changelog before this version |
@ -0,0 +1,22 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Aaron Scherer, Tobias Nyholm |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
|||
|
@ -0,0 +1,31 @@ |
|||
# Void PSR-6 Cache pool |
|||
[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
|||
[![Latest Stable Version](https://poser.pugx.org/cache/void-adapter/v/stable)](https://packagist.org/packages/cache/void-adapter) |
|||
[![codecov.io](https://codecov.io/github/php-cache/void-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/void-adapter?branch=master) |
|||
[![Total Downloads](https://poser.pugx.org/cache/void-adapter/downloads)](https://packagist.org/packages/cache/void-adapter) |
|||
[![Monthly Downloads](https://poser.pugx.org/cache/void-adapter/d/monthly.png)](https://packagist.org/packages/cache/void-adapter) |
|||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) |
|||
|
|||
This is a void implementation of a PSR-6 cache. Other names for this adapter could be Blackhole or Null apdapter. |
|||
This adapter does not save anything and will always return an empty CacheItem. It is a part of the PHP Cache |
|||
organisation. To read about features like tagging and hierarchy support please read the |
|||
shared documentation at [www.php-cache.com](http://www.php-cache.com). |
|||
|
|||
### Install |
|||
|
|||
```bash |
|||
composer require cache/void-adapter |
|||
``` |
|||
|
|||
### Use |
|||
|
|||
You do not need to do any configuration to use the `VoidCachePool`. |
|||
|
|||
```php |
|||
$pool = new VoidCachePool(); |
|||
``` |
|||
|
|||
### Contribute |
|||
|
|||
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or |
|||
report any issues you find on the [issue tracker](http://issues.php-cache.com). |
@ -0,0 +1,30 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Void\Tests; |
|||
|
|||
use Cache\Adapter\Void\VoidCachePool; |
|||
use Cache\IntegrationTests\HierarchicalCachePoolTest; |
|||
|
|||
class IntegrationHierarchyTest extends HierarchicalCachePoolTest |
|||
{ |
|||
protected $skippedTests = [ |
|||
'testBasicUsage' => 'Void adapter does not save,', |
|||
'testChain' => 'Void adapter does not save,', |
|||
'testRemoval' => 'Void adapter does not save,', |
|||
'testRemovalWhenDeferred' => 'Void adapter does not save,', |
|||
]; |
|||
|
|||
public function createCachePool() |
|||
{ |
|||
return new VoidCachePool(); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Void\Tests; |
|||
|
|||
use Cache\Adapter\Void\VoidCachePool; |
|||
use Cache\IntegrationTests\CachePoolTest as BaseTest; |
|||
|
|||
class IntegrationPoolTest extends BaseTest |
|||
{ |
|||
protected $skippedTests = [ |
|||
'testBasicUsage' => 'Void adapter does not save,', |
|||
'testGetItem' => 'Void adapter does not save,', |
|||
'testGetItems' => 'Void adapter does not save,', |
|||
'testHasItem' => 'Void adapter does not save,', |
|||
'testDeleteItems' => 'Void adapter does not save,', |
|||
'testSave' => 'Void adapter does not save,', |
|||
'testSaveWithoutExpire' => 'Void adapter does not save,', |
|||
'testDataTypeFloat' => 'Void adapter only outputs boolean,', |
|||
'testDataTypeBoolean' => 'Void adapter only outputs boolean,', |
|||
'testDataTypeArray' => 'Void adapter only outputs boolean,', |
|||
'testDataTypeObject' => 'Void adapter only outputs boolean,', |
|||
'testBinaryData' => 'Void adapter only outputs boolean,', |
|||
'testDeferredSave' => 'Void adapter does not save,', |
|||
'testDeferredSaveWithoutCommit' => 'Void adapter does not save,', |
|||
'testCommit' => 'Void adapter does not save,', |
|||
'testExpiresAt' => 'Void adapter does not save,', |
|||
'testExpiresAtWithNull' => 'Void adapter does not save,', |
|||
'testExpiresAfterWithNull' => 'Void adapter does not save,', |
|||
'testKeyLength' => 'Void adapter does not save,', |
|||
'testDataTypeString' => 'Void adapter does not save,', |
|||
'testDataTypeInteger' => 'Void adapter does not save,', |
|||
'testDataTypeNull' => 'Void adapter does not save,', |
|||
'testIsHit' => 'Void adapter does not save,', |
|||
'testIsHitDeferred' => 'Void adapter does not save,', |
|||
'testSaveDeferredWhenChangingValues' => 'Void adapter does not save,', |
|||
'testSavingObject' => 'Void adapter does not save,', |
|||
'testSaveDeferredOverwrite' => 'Void adapter does not save,', |
|||
]; |
|||
|
|||
public function createCachePool() |
|||
{ |
|||
return new VoidCachePool(); |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Void\Tests; |
|||
|
|||
use Cache\Adapter\Void\VoidCachePool; |
|||
use Cache\IntegrationTests\SimpleCacheTest as BaseTest; |
|||
|
|||
class IntegrationSimpleCacheTest extends BaseTest |
|||
{ |
|||
protected $skippedTests = [ |
|||
'testSet' => 'Void adapter does not save,', |
|||
'testSetTtl' => 'Void adapter does not save,', |
|||
'testGet' => 'Void adapter does not save,', |
|||
'testSetMultiple' => 'Void adapter does not save,', |
|||
'testSetMultipleTtl' => 'Void adapter does not save,', |
|||
'testSetMultipleWithGenerator' => 'Void adapter does not save,', |
|||
'testGetMultiple' => 'Void adapter does not save,', |
|||
'testGetMultipleWithGenerator' => 'Void adapter does not save,', |
|||
'testHas' => 'Void adapter does not save,', |
|||
'testBinaryData' => 'Void adapter does not save,', |
|||
'testDataTypeString' => 'Void adapter does not save,', |
|||
'testDataTypeInteger' => 'Void adapter does not save,', |
|||
'testDataTypeFloat' => 'Void adapter does not save,', |
|||
'testDataTypeBoolean' => 'Void adapter does not save,', |
|||
'testDataTypeArray' => 'Void adapter does not save,', |
|||
'testDataTypeObject' => 'Void adapter does not save,', |
|||
'testSetValidKeys' => 'Void adapter does not save,', |
|||
'testSetMultipleValidKeys' => 'Void adapter does not save,', |
|||
'testSetMultipleInvalidKeys' => 'Void adapter does not save,', |
|||
'testSetValidData' => 'Void adapter does not save,', |
|||
'testSetMultipleValidData' => 'Void adapter does not save,', |
|||
'testObjectDoesNotChangeInCache' => 'Void adapter does not save,', |
|||
]; |
|||
|
|||
public function createSimpleCache() |
|||
{ |
|||
return new VoidCachePool(); |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Void\Tests; |
|||
|
|||
use Cache\Adapter\Void\VoidCachePool; |
|||
use Cache\IntegrationTests\TaggableCachePoolTest; |
|||
|
|||
class IntegrationTagTest extends TaggableCachePoolTest |
|||
{ |
|||
protected $skippedTests = [ |
|||
'testBasicUsage' => 'Void adapter does not save,', |
|||
'testGetItems' => 'Void adapter does not save,', |
|||
'testHasItem' => 'Void adapter does not save,', |
|||
'testDeleteItem' => 'Void adapter does not save,', |
|||
'testKeysWithDeferred' => 'Void adapter does not save,', |
|||
'testSaveDeferred' => 'Void adapter does not save,', |
|||
'testMultipleTags' => 'Void adapter does not save,', |
|||
'testRemoveTagWhenItemIsRemoved' => 'Void adapter does not save,', |
|||
'testClearPool' => 'Void adapter does not save,', |
|||
'testInvalidateTag' => 'Void adapter does not save,', |
|||
'testInvalidateTags' => 'Void adapter does not save,', |
|||
'testPreviousTag' => 'Void adapter does not save,', |
|||
'testPreviousTagDeferred' => 'Void adapter does not save,', |
|||
'testTagAccessorDuplicateTags' => 'Void adapter does not save,', |
|||
'testTagsAreCleanedOnSave' => 'Void adapter does not save,', |
|||
]; |
|||
|
|||
public function createCachePool() |
|||
{ |
|||
return new VoidCachePool(); |
|||
} |
|||
} |
@ -0,0 +1,80 @@ |
|||
<?php |
|||
|
|||
/* |
|||
* This file is part of php-cache organization. |
|||
* |
|||
* (c) 2015 Aaron Scherer <aequasi@gmail.com>, Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
* |
|||
* This source file is subject to the MIT license that is bundled |
|||
* with this source code in the file LICENSE. |
|||
*/ |
|||
|
|||
namespace Cache\Adapter\Void; |
|||
|
|||
use Cache\Adapter\Common\AbstractCachePool; |
|||
use Cache\Adapter\Common\PhpCacheItem; |
|||
use Cache\Hierarchy\HierarchicalPoolInterface; |
|||
|
|||
/** |
|||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> |
|||
*/ |
|||
class VoidCachePool extends AbstractCachePool implements HierarchicalPoolInterface |
|||
{ |
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function fetchObjectFromCache($key) |
|||
{ |
|||
return [false, null, [], null]; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function clearAllObjectsFromCache() |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function clearOneObjectFromCache($key) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function storeItemInCache(PhpCacheItem $item, $ttl) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function clearTags(array $tags) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
protected function getList($name) |
|||
{ |
|||
return []; |
|||
} |
|||
|
|||
protected function removeList($name) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
protected function appendListItem($name, $key) |
|||
{ |
|||
} |
|||
|
|||
protected function removeListItem($name, $key) |
|||
{ |
|||
} |
|||
} |
@ -0,0 +1,55 @@ |
|||
{ |
|||
"name": "cache/void-adapter", |
|||
"description": "A PSR-6 cache implementation using Void. This implementation supports tags", |
|||
"type": "library", |
|||
"license": "MIT", |
|||
"minimum-stability": "dev", |
|||
"prefer-stable": true, |
|||
"keywords": [ |
|||
"cache", |
|||
"psr-6", |
|||
"void", |
|||
"tag" |
|||
], |
|||
"homepage": "http://www.php-cache.com/en/latest/", |
|||
"authors": [ |
|||
{ |
|||
"name": "Aaron Scherer", |
|||
"email": "aequasi@gmail.com", |
|||
"homepage": "https://github.com/aequasi" |
|||
}, |
|||
{ |
|||
"name": "Tobias Nyholm", |
|||
"email": "tobias.nyholm@gmail.com", |
|||
"homepage": "https://github.com/nyholm" |
|||
} |
|||
], |
|||
"require": { |
|||
"php": "^5.6 || ^7.0", |
|||
"psr/cache": "^1.0", |
|||
"psr/simple-cache": "^1.0", |
|||
"cache/adapter-common": "^1.0", |
|||
"cache/hierarchical-cache": "^1.0" |
|||
}, |
|||
"require-dev": { |
|||
"phpunit/phpunit": "^5.7.21", |
|||
"cache/integration-tests": "^0.16" |
|||
}, |
|||
"provide": { |
|||
"psr/cache-implementation": "^1.0", |
|||
"psr/simple-cache-implementation": "^1.0" |
|||
}, |
|||
"autoload": { |
|||
"psr-4": { |
|||
"Cache\\Adapter\\Void\\": "" |
|||
}, |
|||
"exclude-from-classmap": [ |
|||
"/Tests/" |
|||
] |
|||
}, |
|||
"extra": { |
|||
"branch-alias": { |
|||
"dev-master": "1.1-dev" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<phpunit backupGlobals="false" |
|||
backupStaticAttributes="false" |
|||
colors="true" |
|||
convertErrorsToExceptions="true" |
|||
convertNoticesToExceptions="true" |
|||
convertWarningsToExceptions="true" |
|||
processIsolation="false" |
|||
stopOnFailure="false" |
|||
syntaxCheck="false" |
|||
bootstrap="vendor/autoload.php" |
|||
> |
|||
<testsuites> |
|||
<testsuite name="Main Test Suite"> |
|||
<directory>./Tests/</directory> |
|||
</testsuite> |
|||
</testsuites> |
|||
|
|||
<groups> |
|||
<exclude> |
|||
<group>benchmark</group> |
|||
</exclude> |
|||
</groups> |
|||
|
|||
<filter> |
|||
<whitelist> |
|||
<directory>./</directory> |
|||
<exclude> |
|||
<directory>./Tests</directory> |
|||
<directory>./vendor</directory> |
|||
</exclude> |
|||
</whitelist> |
|||
</filter> |
|||
</phpunit> |
@ -0,0 +1,696 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 1.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache; |
|||
|
|||
use Cake\Cache\Engine\NullEngine; |
|||
use Cake\Core\ObjectRegistry; |
|||
use Cake\Core\StaticConfigTrait; |
|||
use InvalidArgumentException; |
|||
use RuntimeException; |
|||
|
|||
/** |
|||
* Cache provides a consistent interface to Caching in your application. It allows you |
|||
* to use several different Cache engines, without coupling your application to a specific |
|||
* implementation. It also allows you to change out cache storage or configuration without effecting |
|||
* the rest of your application. |
|||
* |
|||
* ### Configuring Cache engines |
|||
* |
|||
* You can configure Cache engines in your application's `Config/cache.php` file. |
|||
* A sample configuration would be: |
|||
* |
|||
* ``` |
|||
* Cache::config('shared', [ |
|||
* 'className' => 'Cake\Cache\Engine\ApcuEngine', |
|||
* 'prefix' => 'my_app_' |
|||
* ]); |
|||
* ``` |
|||
* |
|||
* This would configure an APCu cache engine to the 'shared' alias. You could then read and write |
|||
* to that cache alias by using it for the `$config` parameter in the various Cache methods. |
|||
* |
|||
* In general all Cache operations are supported by all cache engines. |
|||
* However, Cache::increment() and Cache::decrement() are not supported by File caching. |
|||
* |
|||
* There are 7 built-in caching engines: |
|||
* |
|||
* - `ApcuEngine` - Uses the APCu object cache, one of the fastest caching engines. |
|||
* - `ArrayEngine` - Uses only memory to store all data, not actually a persistent engine. |
|||
* Can be useful in test or CLI environment. |
|||
* - `FileEngine` - Uses simple files to store content. Poor performance, but good for |
|||
* storing large objects, or things that are not IO sensitive. Well suited to development |
|||
* as it is an easy cache to inspect and manually flush. |
|||
* - `MemcacheEngine` - Uses the PECL::Memcache extension and Memcached for storage. |
|||
* Fast reads/writes, and benefits from memcache being distributed. |
|||
* - `RedisEngine` - Uses redis and php-redis extension to store cache data. |
|||
* - `WincacheEngine` - Uses Windows Cache Extension for PHP. Supports wincache 1.1.0 and higher. |
|||
* This engine is recommended to people deploying on windows with IIS. |
|||
* - `XcacheEngine` - Uses the Xcache extension, an alternative to APCu. |
|||
* |
|||
* See Cache engine documentation for expected configuration keys. |
|||
* |
|||
* @see config/app.php for configuration settings |
|||
*/ |
|||
class Cache |
|||
{ |
|||
use StaticConfigTrait; |
|||
|
|||
/** |
|||
* An array mapping url schemes to fully qualified caching engine |
|||
* class names. |
|||
* |
|||
* @var string[] |
|||
*/ |
|||
protected static $_dsnClassMap = [ |
|||
'array' => 'Cake\Cache\Engine\ArrayEngine', |
|||
'apc' => 'Cake\Cache\Engine\ApcuEngine', // @deprecated Since 3.6. Use apcu instead. |
|||
'apcu' => 'Cake\Cache\Engine\ApcuEngine', |
|||
'file' => 'Cake\Cache\Engine\FileEngine', |
|||
'memcached' => 'Cake\Cache\Engine\MemcachedEngine', |
|||
'null' => 'Cake\Cache\Engine\NullEngine', |
|||
'redis' => 'Cake\Cache\Engine\RedisEngine', |
|||
'wincache' => 'Cake\Cache\Engine\WincacheEngine', |
|||
'xcache' => 'Cake\Cache\Engine\XcacheEngine', |
|||
]; |
|||
|
|||
/** |
|||
* Flag for tracking whether or not caching is enabled. |
|||
* |
|||
* @var bool |
|||
*/ |
|||
protected static $_enabled = true; |
|||
|
|||
/** |
|||
* Group to Config mapping |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected static $_groups = []; |
|||
|
|||
/** |
|||
* Cache Registry used for creating and using cache adapters. |
|||
* |
|||
* @var \Cake\Core\ObjectRegistry |
|||
*/ |
|||
protected static $_registry; |
|||
|
|||
/** |
|||
* Returns the Cache Registry instance used for creating and using cache adapters. |
|||
* |
|||
* @return \Cake\Core\ObjectRegistry |
|||
*/ |
|||
public static function getRegistry() |
|||
{ |
|||
if (!static::$_registry) { |
|||
static::$_registry = new CacheRegistry(); |
|||
} |
|||
|
|||
return static::$_registry; |
|||
} |
|||
|
|||
/** |
|||
* Sets the Cache Registry instance used for creating and using cache adapters. |
|||
* |
|||
* Also allows for injecting of a new registry instance. |
|||
* |
|||
* @param \Cake\Core\ObjectRegistry $registry Injectable registry object. |
|||
* @return void |
|||
*/ |
|||
public static function setRegistry(ObjectRegistry $registry) |
|||
{ |
|||
static::$_registry = $registry; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Cache Registry instance used for creating and using cache adapters. |
|||
* Also allows for injecting of a new registry instance. |
|||
* |
|||
* @param \Cake\Core\ObjectRegistry|null $registry Injectable registry object. |
|||
* @return \Cake\Core\ObjectRegistry |
|||
* @deprecated Deprecated since 3.5. Use getRegistry() and setRegistry() instead. |
|||
*/ |
|||
public static function registry(ObjectRegistry $registry = null) |
|||
{ |
|||
deprecationWarning('Use Cache::getRegistry() and Cache::setRegistry() instead.'); |
|||
if ($registry) { |
|||
static::setRegistry($registry); |
|||
} |
|||
|
|||
return static::getRegistry(); |
|||
} |
|||
|
|||
/** |
|||
* Finds and builds the instance of the required engine class. |
|||
* |
|||
* @param string $name Name of the config array that needs an engine instance built |
|||
* @return void |
|||
* @throws \InvalidArgumentException When a cache engine cannot be created. |
|||
*/ |
|||
protected static function _buildEngine($name) |
|||
{ |
|||
$registry = static::getRegistry(); |
|||
|
|||
if (empty(static::$_config[$name]['className'])) { |
|||
throw new InvalidArgumentException( |
|||
sprintf('The "%s" cache configuration does not exist.', $name) |
|||
); |
|||
} |
|||
|
|||
$config = static::$_config[$name]; |
|||
|
|||
try { |
|||
$registry->load($name, $config); |
|||
} catch (RuntimeException $e) { |
|||
if (!array_key_exists('fallback', $config)) { |
|||
$registry->set($name, new NullEngine()); |
|||
trigger_error($e->getMessage(), E_USER_WARNING); |
|||
|
|||
return; |
|||
} |
|||
|
|||
if ($config['fallback'] === false) { |
|||
throw $e; |
|||
} |
|||
|
|||
if ($config['fallback'] === $name) { |
|||
throw new InvalidArgumentException(sprintf('"%s" cache configuration cannot fallback to itself.', $name), null, $e); |
|||
} |
|||
|
|||
$fallbackEngine = clone static::engine($config['fallback']); |
|||
$newConfig = $config + ['groups' => [], 'prefix' => null]; |
|||
$fallbackEngine->setConfig('groups', $newConfig['groups'], false); |
|||
if ($newConfig['prefix']) { |
|||
$fallbackEngine->setConfig('prefix', $newConfig['prefix'], false); |
|||
} |
|||
$registry->set($name, $fallbackEngine); |
|||
} |
|||
|
|||
if ($config['className'] instanceof CacheEngine) { |
|||
$config = $config['className']->getConfig(); |
|||
} |
|||
|
|||
if (!empty($config['groups'])) { |
|||
foreach ($config['groups'] as $group) { |
|||
static::$_groups[$group][] = $name; |
|||
static::$_groups[$group] = array_unique(static::$_groups[$group]); |
|||
sort(static::$_groups[$group]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Fetch the engine attached to a specific configuration name. |
|||
* |
|||
* If the cache engine & configuration are missing an error will be |
|||
* triggered. |
|||
* |
|||
* @param string $config The configuration name you want an engine for. |
|||
* @return \Cake\Cache\CacheEngine When caching is disabled a null engine will be returned. |
|||
* @deprecated 3.7.0 Use Cache::pool() instead. In 4.0 all cache engines will implement the |
|||
* PSR16 interface and this method does not return objects implementing that interface. |
|||
*/ |
|||
public static function engine($config) |
|||
{ |
|||
if (!static::$_enabled) { |
|||
return new NullEngine(); |
|||
} |
|||
|
|||
$registry = static::getRegistry(); |
|||
|
|||
if (isset($registry->{$config})) { |
|||
return $registry->{$config}; |
|||
} |
|||
|
|||
static::_buildEngine($config); |
|||
|
|||
return $registry->{$config}; |
|||
} |
|||
|
|||
/** |
|||
* Get a SimpleCacheEngine object for the named cache pool. |
|||
* |
|||
* @param string $config The name of the configured cache backend. |
|||
* @return \Cake\Cache\SimpleCacheEngine |
|||
*/ |
|||
public static function pool($config) |
|||
{ |
|||
return new SimpleCacheEngine(static::engine($config)); |
|||
} |
|||
|
|||
/** |
|||
* Garbage collection |
|||
* |
|||
* Permanently remove all expired and deleted data |
|||
* |
|||
* @param string $config [optional] The config name you wish to have garbage collected. Defaults to 'default' |
|||
* @param int|null $expires [optional] An expires timestamp. Defaults to NULL |
|||
* @return void |
|||
* @deprecated 3.7.0 Will be removed in 4.0 |
|||
*/ |
|||
public static function gc($config = 'default', $expires = null) |
|||
{ |
|||
$engine = static::engine($config); |
|||
$engine->gc($expires); |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Writing to the active cache config: |
|||
* |
|||
* ``` |
|||
* Cache::write('cached_data', $data); |
|||
* ``` |
|||
* |
|||
* Writing to a specific cache config: |
|||
* |
|||
* ``` |
|||
* Cache::write('cached_data', $data, 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached - anything except a resource |
|||
* @param string $config Optional string configuration name to write to. Defaults to 'default' |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public static function write($key, $value, $config = 'default') |
|||
{ |
|||
if (is_resource($value)) { |
|||
return false; |
|||
} |
|||
|
|||
$backend = static::pool($config); |
|||
$success = $backend->set($key, $value); |
|||
if ($success === false && $value !== '') { |
|||
trigger_error( |
|||
sprintf( |
|||
"%s cache was unable to write '%s' to %s cache", |
|||
$config, |
|||
$key, |
|||
get_class($backend) |
|||
), |
|||
E_USER_WARNING |
|||
); |
|||
} |
|||
|
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* Write data for many keys into cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Writing to the active cache config: |
|||
* |
|||
* ``` |
|||
* Cache::writeMany(['cached_data_1' => 'data 1', 'cached_data_2' => 'data 2']); |
|||
* ``` |
|||
* |
|||
* Writing to a specific cache config: |
|||
* |
|||
* ``` |
|||
* Cache::writeMany(['cached_data_1' => 'data 1', 'cached_data_2' => 'data 2'], 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param array $data An array of data to be stored in the cache |
|||
* @param string $config Optional string configuration name to write to. Defaults to 'default' |
|||
* @return array of bools for each key provided, indicating true for success or false for fail |
|||
* @throws \RuntimeException |
|||
*/ |
|||
public static function writeMany($data, $config = 'default') |
|||
{ |
|||
$engine = static::engine($config); |
|||
|
|||
$return = $engine->writeMany($data); |
|||
foreach ($return as $key => $success) { |
|||
if ($success === false && $data[$key] !== '') { |
|||
throw new RuntimeException(sprintf( |
|||
'%s cache was unable to write \'%s\' to %s cache', |
|||
$config, |
|||
$key, |
|||
get_class($engine) |
|||
)); |
|||
} |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Reading from the active cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::read('my_data'); |
|||
* ``` |
|||
* |
|||
* Reading from a specific cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::read('my_data', 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param string $config optional name of the configuration to use. Defaults to 'default' |
|||
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it |
|||
*/ |
|||
public static function read($key, $config = 'default') |
|||
{ |
|||
// TODO In 4.x this needs to change to use pool() |
|||
$engine = static::engine($config); |
|||
|
|||
return $engine->read($key); |
|||
} |
|||
|
|||
/** |
|||
* Read multiple keys from the cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Reading multiple keys from the active cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::readMany(['my_data_1', 'my_data_2]); |
|||
* ``` |
|||
* |
|||
* Reading from a specific cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::readMany(['my_data_1', 'my_data_2], 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param array $keys an array of keys to fetch from the cache |
|||
* @param string $config optional name of the configuration to use. Defaults to 'default' |
|||
* @return array An array containing, for each of the given $keys, the cached data or false if cached data could not be |
|||
* retrieved. |
|||
*/ |
|||
public static function readMany($keys, $config = 'default') |
|||
{ |
|||
// In 4.x this needs to change to use pool() |
|||
$engine = static::engine($config); |
|||
|
|||
return $engine->readMany($keys); |
|||
} |
|||
|
|||
/** |
|||
* Increment a number under the key and return incremented value. |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to add |
|||
* @param string $config Optional string configuration name. Defaults to 'default' |
|||
* @return int|false New value, or false if the data doesn't exist, is not integer, |
|||
* or if there was an error fetching it. |
|||
*/ |
|||
public static function increment($key, $offset = 1, $config = 'default') |
|||
{ |
|||
$engine = static::pool($config); |
|||
if (!is_int($offset) || $offset < 0) { |
|||
return false; |
|||
} |
|||
|
|||
return $engine->increment($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Decrement a number under the key and return decremented value. |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @param string $config Optional string configuration name. Defaults to 'default' |
|||
* @return int|false New value, or false if the data doesn't exist, is not integer, |
|||
* or if there was an error fetching it |
|||
*/ |
|||
public static function decrement($key, $offset = 1, $config = 'default') |
|||
{ |
|||
$engine = static::pool($config); |
|||
if (!is_int($offset) || $offset < 0) { |
|||
return false; |
|||
} |
|||
|
|||
return $engine->decrement($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Deleting from the active cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::delete('my_data'); |
|||
* ``` |
|||
* |
|||
* Deleting from a specific cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::delete('my_data', 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param string $config name of the configuration to use. Defaults to 'default' |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public static function delete($key, $config = 'default') |
|||
{ |
|||
$backend = static::pool($config); |
|||
|
|||
return $backend->delete($key); |
|||
} |
|||
|
|||
/** |
|||
* Delete many keys from the cache. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Deleting multiple keys from the active cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::deleteMany(['my_data_1', 'my_data_2']); |
|||
* ``` |
|||
* |
|||
* Deleting from a specific cache configuration. |
|||
* |
|||
* ``` |
|||
* Cache::deleteMany(['my_data_1', 'my_data_2], 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param array $keys Array of cache keys to be deleted |
|||
* @param string $config name of the configuration to use. Defaults to 'default' |
|||
* @return array of boolean values that are true if the value was successfully deleted, |
|||
* false if it didn't exist or couldn't be removed. |
|||
*/ |
|||
public static function deleteMany($keys, $config = 'default') |
|||
{ |
|||
$backend = static::pool($config); |
|||
|
|||
$return = []; |
|||
foreach ($keys as $key) { |
|||
$return[$key] = $backend->delete($key); |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache. |
|||
* |
|||
* @param bool $check if true will check expiration, otherwise delete all. This parameter |
|||
* will become a no-op value in 4.0 as it is deprecated. |
|||
* @param string $config name of the configuration to use. Defaults to 'default' |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
public static function clear($check = false, $config = 'default') |
|||
{ |
|||
$engine = static::engine($config); |
|||
|
|||
return $engine->clear($check); |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache from all configurations. |
|||
* |
|||
* @param bool $check if true will check expiration, otherwise delete all. This parameter |
|||
* will become a no-op value in 4.0 as it is deprecated. |
|||
* @return array Status code. For each configuration, it reports the status of the operation |
|||
*/ |
|||
public static function clearAll($check = false) |
|||
{ |
|||
$status = []; |
|||
|
|||
foreach (self::configured() as $config) { |
|||
$status[$config] = self::clear($check, $config); |
|||
} |
|||
|
|||
return $status; |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache belonging to the same group. |
|||
* |
|||
* @param string $group name of the group to be cleared |
|||
* @param string $config name of the configuration to use. Defaults to 'default' |
|||
* @return bool True if the cache group was successfully cleared, false otherwise |
|||
*/ |
|||
public static function clearGroup($group, $config = 'default') |
|||
{ |
|||
$engine = static::pool($config); |
|||
|
|||
return $engine->clearGroup($group); |
|||
} |
|||
|
|||
/** |
|||
* Retrieve group names to config mapping. |
|||
* |
|||
* ``` |
|||
* Cache::config('daily', ['duration' => '1 day', 'groups' => ['posts']]); |
|||
* Cache::config('weekly', ['duration' => '1 week', 'groups' => ['posts', 'archive']]); |
|||
* $configs = Cache::groupConfigs('posts'); |
|||
* ``` |
|||
* |
|||
* $configs will equal to `['posts' => ['daily', 'weekly']]` |
|||
* Calling this method will load all the configured engines. |
|||
* |
|||
* @param string|null $group group name or null to retrieve all group mappings |
|||
* @return array map of group and all configuration that has the same group |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public static function groupConfigs($group = null) |
|||
{ |
|||
foreach (array_keys(static::$_config) as $config) { |
|||
static::engine($config); |
|||
} |
|||
if ($group === null) { |
|||
return static::$_groups; |
|||
} |
|||
|
|||
if (isset(self::$_groups[$group])) { |
|||
return [$group => self::$_groups[$group]]; |
|||
} |
|||
|
|||
throw new InvalidArgumentException(sprintf('Invalid cache group %s', $group)); |
|||
} |
|||
|
|||
/** |
|||
* Re-enable caching. |
|||
* |
|||
* If caching has been disabled with Cache::disable() this method will reverse that effect. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public static function enable() |
|||
{ |
|||
static::$_enabled = true; |
|||
} |
|||
|
|||
/** |
|||
* Disable caching. |
|||
* |
|||
* When disabled all cache operations will return null. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public static function disable() |
|||
{ |
|||
static::$_enabled = false; |
|||
} |
|||
|
|||
/** |
|||
* Check whether or not caching is enabled. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
public static function enabled() |
|||
{ |
|||
return static::$_enabled; |
|||
} |
|||
|
|||
/** |
|||
* Provides the ability to easily do read-through caching. |
|||
* |
|||
* When called if the $key is not set in $config, the $callable function |
|||
* will be invoked. The results will then be stored into the cache config |
|||
* at key. |
|||
* |
|||
* Examples: |
|||
* |
|||
* Using a Closure to provide data, assume `$this` is a Table object: |
|||
* |
|||
* ``` |
|||
* $results = Cache::remember('all_articles', function () { |
|||
* return $this->find('all'); |
|||
* }); |
|||
* ``` |
|||
* |
|||
* @param string $key The cache key to read/store data at. |
|||
* @param callable $callable The callable that provides data in the case when |
|||
* the cache key is empty. Can be any callable type supported by your PHP. |
|||
* @param string $config The cache configuration to use for this operation. |
|||
* Defaults to default. |
|||
* @return mixed If the key is found: the cached data, false if the data |
|||
* missing/expired, or an error. If the key is not found: boolean of the |
|||
* success of the write |
|||
*/ |
|||
public static function remember($key, $callable, $config = 'default') |
|||
{ |
|||
$existing = self::read($key, $config); |
|||
if ($existing !== false) { |
|||
return $existing; |
|||
} |
|||
$results = call_user_func($callable); |
|||
self::write($key, $results, $config); |
|||
|
|||
return $results; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into a cache engine if it doesn't exist already. |
|||
* |
|||
* ### Usage: |
|||
* |
|||
* Writing to the active cache config: |
|||
* |
|||
* ``` |
|||
* Cache::add('cached_data', $data); |
|||
* ``` |
|||
* |
|||
* Writing to a specific cache config: |
|||
* |
|||
* ``` |
|||
* Cache::add('cached_data', $data, 'long_term'); |
|||
* ``` |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached - anything except a resource. |
|||
* @param string $config Optional string configuration name to write to. Defaults to 'default'. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
* Or if the key existed already. |
|||
*/ |
|||
public static function add($key, $value, $config = 'default') |
|||
{ |
|||
$pool = static::pool($config); |
|||
if (is_resource($value)) { |
|||
return false; |
|||
} |
|||
|
|||
return $pool->add($key, $value); |
|||
} |
|||
} |
@ -0,0 +1,295 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 1.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache; |
|||
|
|||
use Cake\Core\InstanceConfigTrait; |
|||
use InvalidArgumentException; |
|||
|
|||
/** |
|||
* Storage engine for CakePHP caching |
|||
*/ |
|||
abstract class CacheEngine |
|||
{ |
|||
use InstanceConfigTrait; |
|||
|
|||
/** |
|||
* The default cache configuration is overridden in most cache adapters. These are |
|||
* the keys that are common to all adapters. If overridden, this property is not used. |
|||
* |
|||
* - `duration` Specify how long items in this cache configuration last. |
|||
* - `groups` List of groups or 'tags' associated to every key stored in this config. |
|||
* handy for deleting a complete group from cache. |
|||
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace |
|||
* with either another cache config or another application. |
|||
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable |
|||
* cache::gc from ever being called automatically. |
|||
* - `warnOnWriteFailures` Some engines, such as ApcuEngine, may raise warnings on |
|||
* write failures. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_defaultConfig = [ |
|||
'duration' => 3600, |
|||
'groups' => [], |
|||
'prefix' => 'cake_', |
|||
'probability' => 100, |
|||
'warnOnWriteFailures' => true, |
|||
]; |
|||
|
|||
/** |
|||
* Contains the compiled string with all groups |
|||
* prefixes to be prepended to every key in this cache engine |
|||
* |
|||
* @var string |
|||
*/ |
|||
protected $_groupPrefix; |
|||
|
|||
/** |
|||
* Initialize the cache engine |
|||
* |
|||
* Called automatically by the cache frontend. Merge the runtime config with the defaults |
|||
* before use. |
|||
* |
|||
* @param array $config Associative array of parameters for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
$this->setConfig($config); |
|||
|
|||
if (!empty($this->_config['groups'])) { |
|||
sort($this->_config['groups']); |
|||
$this->_groupPrefix = str_repeat('%s_', count($this->_config['groups'])); |
|||
} |
|||
if (!is_numeric($this->_config['duration'])) { |
|||
$this->_config['duration'] = strtotime($this->_config['duration']) - time(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Garbage collection |
|||
* |
|||
* Permanently remove all expired and deleted data |
|||
* |
|||
* @param int|null $expires [optional] An expires timestamp, invalidating all data before. |
|||
* @return void |
|||
*/ |
|||
public function gc($expires = null) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* Write value for a key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
abstract public function write($key, $value); |
|||
|
|||
/** |
|||
* Write data for many keys into cache |
|||
* |
|||
* @param array $data An array of data to be stored in the cache |
|||
* @return array of bools for each key provided, true if the data was successfully cached, false on failure |
|||
*/ |
|||
public function writeMany($data) |
|||
{ |
|||
$return = []; |
|||
foreach ($data as $key => $value) { |
|||
$return[$key] = $this->write($key, $value); |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it |
|||
*/ |
|||
abstract public function read($key); |
|||
|
|||
/** |
|||
* Read multiple keys from the cache |
|||
* |
|||
* @param array $keys An array of identifiers for the data |
|||
* @return array For each cache key (given as the array key) the cache data associated or false if the data doesn't |
|||
* exist, has expired, or if there was an error fetching it |
|||
*/ |
|||
public function readMany($keys) |
|||
{ |
|||
$return = []; |
|||
foreach ($keys as $key) { |
|||
$return[$key] = $this->read($key); |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Increment a number under the key and return incremented value |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to add |
|||
* @return bool|int New incremented value, false otherwise |
|||
*/ |
|||
abstract public function increment($key, $offset = 1); |
|||
|
|||
/** |
|||
* Decrement a number under the key and return decremented value |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return bool|int New incremented value, false otherwise |
|||
*/ |
|||
abstract public function decrement($key, $offset = 1); |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
abstract public function delete($key); |
|||
|
|||
/** |
|||
* Delete all keys from the cache |
|||
* |
|||
* @param bool $check if true will check expiration, otherwise delete all |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
abstract public function clear($check); |
|||
|
|||
/** |
|||
* Deletes keys from the cache |
|||
* |
|||
* @param array $keys An array of identifiers for the data |
|||
* @return array For each provided cache key (given back as the array key) true if the value was successfully deleted, |
|||
* false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public function deleteMany($keys) |
|||
{ |
|||
$return = []; |
|||
foreach ($keys as $key) { |
|||
$return[$key] = $this->delete($key); |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Add a key to the cache if it does not already exist. |
|||
* |
|||
* Defaults to a non-atomic implementation. Subclasses should |
|||
* prefer atomic implementations. |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
*/ |
|||
public function add($key, $value) |
|||
{ |
|||
$cachedValue = $this->read($key); |
|||
if ($cachedValue === false) { |
|||
return $this->write($key, $value); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Clears all values belonging to a group. Is up to the implementing engine |
|||
* to decide whether actually delete the keys or just simulate it to achieve |
|||
* the same result. |
|||
* |
|||
* @param string $group name of the group to be cleared |
|||
* @return bool |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Does whatever initialization for each group is required |
|||
* and returns the `group value` for each of them, this is |
|||
* the token representing each group in the cache key |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
return $this->_config['groups']; |
|||
} |
|||
|
|||
/** |
|||
* Generates a safe key for use with cache engine storage engines. |
|||
* |
|||
* @param string $key the key passed over |
|||
* @return string|false string key or false |
|||
*/ |
|||
public function key($key) |
|||
{ |
|||
if (!$key) { |
|||
return false; |
|||
} |
|||
|
|||
$prefix = ''; |
|||
if ($this->_groupPrefix) { |
|||
$prefix = md5(implode('_', $this->groups())); |
|||
} |
|||
|
|||
$key = preg_replace('/[\s]+/', '_', strtolower(trim(str_replace([DIRECTORY_SEPARATOR, '/', '.'], '_', (string)$key)))); |
|||
|
|||
return $prefix . $key; |
|||
} |
|||
|
|||
/** |
|||
* Generates a safe key, taking account of the configured key prefix |
|||
* |
|||
* @param string $key the key passed over |
|||
* @return string Key |
|||
* @throws \InvalidArgumentException If key's value is empty |
|||
*/ |
|||
protected function _key($key) |
|||
{ |
|||
$key = $this->key($key); |
|||
if ($key === false) { |
|||
throw new InvalidArgumentException('An empty value is not valid as a cache key'); |
|||
} |
|||
|
|||
return $this->_config['prefix'] . $key; |
|||
} |
|||
|
|||
/** |
|||
* Cache Engines may trigger warnings if they encounter failures during operation, |
|||
* if option warnOnWriteFailures is set to true. |
|||
* |
|||
* @param string $message The warning message. |
|||
* @return void |
|||
*/ |
|||
protected function warning($message) |
|||
{ |
|||
if ($this->getConfig('warnOnWriteFailures') !== true) { |
|||
return; |
|||
} |
|||
|
|||
triggerWarning($message); |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.7.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache; |
|||
|
|||
/** |
|||
* Interface for cache engines that defines methods |
|||
* outside of the PSR16 interface that are used by `Cache`. |
|||
* |
|||
* Internally Cache uses this interface when calling engine |
|||
* methods. |
|||
* |
|||
* @since 3.7.0 |
|||
*/ |
|||
interface CacheEngineInterface |
|||
{ |
|||
/** |
|||
* Write data for key into a cache engine if it doesn't exist already. |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached - anything except a resource. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
* Or if the key existed already. |
|||
*/ |
|||
public function add($key, $value); |
|||
|
|||
/** |
|||
* Increment a number under the key and return incremented value |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to add |
|||
* @return bool|int New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1); |
|||
|
|||
/** |
|||
* Decrement a number under the key and return decremented value |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return bool|int New incremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1); |
|||
|
|||
/** |
|||
* Clear all values belonging to the named group. |
|||
* |
|||
* Each implementation needs to decide whether actually |
|||
* delete the keys or just augment a group generation value |
|||
* to achieve the same result. |
|||
* |
|||
* @param string $group name of the group to be cleared |
|||
* @return bool |
|||
*/ |
|||
public function clearGroup($group); |
|||
} |
@ -0,0 +1,113 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.0.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache; |
|||
|
|||
use BadMethodCallException; |
|||
use Cake\Core\App; |
|||
use Cake\Core\ObjectRegistry; |
|||
use RuntimeException; |
|||
|
|||
/** |
|||
* An object registry for cache engines. |
|||
* |
|||
* Used by Cake\Cache\Cache to load and manage cache engines. |
|||
*/ |
|||
class CacheRegistry extends ObjectRegistry |
|||
{ |
|||
/** |
|||
* Resolve a cache engine classname. |
|||
* |
|||
* Part of the template method for Cake\Core\ObjectRegistry::load() |
|||
* |
|||
* @param string $class Partial classname to resolve. |
|||
* @return string|false Either the correct classname or false. |
|||
*/ |
|||
protected function _resolveClassName($class) |
|||
{ |
|||
if (is_object($class)) { |
|||
return $class; |
|||
} |
|||
|
|||
return App::className($class, 'Cache/Engine', 'Engine'); |
|||
} |
|||
|
|||
/** |
|||
* Throws an exception when a cache engine is missing. |
|||
* |
|||
* Part of the template method for Cake\Core\ObjectRegistry::load() |
|||
* |
|||
* @param string $class The classname that is missing. |
|||
* @param string $plugin The plugin the cache is missing in. |
|||
* @return void |
|||
* @throws \BadMethodCallException |
|||
*/ |
|||
protected function _throwMissingClassError($class, $plugin) |
|||
{ |
|||
throw new BadMethodCallException(sprintf('Cache engine %s is not available.', $class)); |
|||
} |
|||
|
|||
/** |
|||
* Create the cache engine instance. |
|||
* |
|||
* Part of the template method for Cake\Core\ObjectRegistry::load() |
|||
* |
|||
* @param string|\Cake\Cache\CacheEngine $class The classname or object to make. |
|||
* @param string $alias The alias of the object. |
|||
* @param array $config An array of settings to use for the cache engine. |
|||
* @return \Cake\Cache\CacheEngine The constructed CacheEngine class. |
|||
* @throws \RuntimeException when an object doesn't implement the correct interface. |
|||
*/ |
|||
protected function _create($class, $alias, $config) |
|||
{ |
|||
if (is_object($class)) { |
|||
$instance = $class; |
|||
} |
|||
|
|||
unset($config['className']); |
|||
if (!isset($instance)) { |
|||
$instance = new $class($config); |
|||
} |
|||
|
|||
if (!($instance instanceof CacheEngine)) { |
|||
throw new RuntimeException( |
|||
'Cache engines must use Cake\Cache\CacheEngine as a base class.' |
|||
); |
|||
} |
|||
|
|||
if (!$instance->init($config)) { |
|||
throw new RuntimeException( |
|||
sprintf('Cache engine %s is not properly configured.', get_class($instance)) |
|||
); |
|||
} |
|||
|
|||
$config = $instance->getConfig(); |
|||
if (isset($config['probability']) && time() % $config['probability'] === 0) { |
|||
$instance->gc(); |
|||
} |
|||
|
|||
return $instance; |
|||
} |
|||
|
|||
/** |
|||
* Remove a single adapter from the registry. |
|||
* |
|||
* @param string $name The adapter name. |
|||
* @return void |
|||
*/ |
|||
public function unload($name) |
|||
{ |
|||
unset($this->_loaded[$name]); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 1.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
// @deprecated 3.6.0 Add backwards compat alias. |
|||
class_alias('Cake\Cache\Engine\ApcuEngine', 'Cake\Cache\Engine\ApcEngine'); |
|||
|
|||
deprecationWarning('Use Cake\Cache\Engine\ApcuEngine instead of Cake\Cache\Engine\ApcEngine.'); |
@ -0,0 +1,234 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.5.4 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use APCuIterator; |
|||
use Cake\Cache\CacheEngine; |
|||
|
|||
/** |
|||
* APCu storage engine for cache |
|||
*/ |
|||
class ApcuEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* Contains the compiled group names |
|||
* (prefixed with the global configuration prefix) |
|||
* |
|||
* @var string[] |
|||
*/ |
|||
protected $_compiledGroupNames = []; |
|||
|
|||
/** |
|||
* Initialize the Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
if (!extension_loaded('apcu')) { |
|||
return false; |
|||
} |
|||
|
|||
return parent::init($config); |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
* @link https://secure.php.net/manual/en/function.apcu-store.php |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
$duration = $this->_config['duration']; |
|||
|
|||
return apcu_store($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, |
|||
* has expired, or if there was an error fetching it |
|||
* @link https://secure.php.net/manual/en/function.apcu-fetch.php |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return apcu_fetch($key); |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
* @link https://secure.php.net/manual/en/function.apcu-inc.php |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return apcu_inc($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
* @link https://secure.php.net/manual/en/function.apcu-dec.php |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return apcu_dec($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
* @link https://secure.php.net/manual/en/function.apcu-delete.php |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return apcu_delete($key); |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache. This will clear every cache config using APC. |
|||
* |
|||
* @param bool $check If true, nothing will be cleared, as entries are removed |
|||
* from APC as they expired. This flag is really only used by FileEngine. |
|||
* @return bool True Returns true. |
|||
* @link https://secure.php.net/manual/en/function.apcu-cache-info.php |
|||
* @link https://secure.php.net/manual/en/function.apcu-delete.php |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
if ($check) { |
|||
return true; |
|||
} |
|||
if (class_exists('APCuIterator', false)) { |
|||
$iterator = new APCuIterator( |
|||
'/^' . preg_quote($this->_config['prefix'], '/') . '/', |
|||
APC_ITER_NONE |
|||
); |
|||
apcu_delete($iterator); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
$cache = apcu_cache_info(); // Raises warning by itself already |
|||
foreach ($cache['cache_list'] as $key) { |
|||
if (strpos($key['info'], $this->_config['prefix']) === 0) { |
|||
apcu_delete($key['info']); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache if it doesn't exist already. |
|||
* If it already exists, it fails and returns false. |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
* @link https://secure.php.net/manual/en/function.apcu-add.php |
|||
*/ |
|||
public function add($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
$duration = $this->_config['duration']; |
|||
|
|||
return apcu_add($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
* @link https://secure.php.net/manual/en/function.apcu-fetch.php |
|||
* @link https://secure.php.net/manual/en/function.apcu-store.php |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
if (empty($this->_compiledGroupNames)) { |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group; |
|||
} |
|||
} |
|||
|
|||
$success = false; |
|||
$groups = apcu_fetch($this->_compiledGroupNames, $success); |
|||
if ($success && count($groups) !== count($this->_config['groups'])) { |
|||
foreach ($this->_compiledGroupNames as $group) { |
|||
if (!isset($groups[$group])) { |
|||
$value = 1; |
|||
if (apcu_store($group, $value) === false) { |
|||
$this->warning( |
|||
sprintf('Failed to store key "%s" with value "%s" into APCu cache.', $group, $value) |
|||
); |
|||
} |
|||
$groups[$group] = $value; |
|||
} |
|||
} |
|||
ksort($groups); |
|||
} |
|||
|
|||
$result = []; |
|||
$groups = array_values($groups); |
|||
foreach ($this->_config['groups'] as $i => $group) { |
|||
$result[] = $group . $groups[$i]; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group The group to clear. |
|||
* @return bool success |
|||
* @link https://secure.php.net/manual/en/function.apcu-inc.php |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
$success = false; |
|||
apcu_inc($this->_config['prefix'] . $group, 1, $success); |
|||
|
|||
return $success; |
|||
} |
|||
} |
@ -0,0 +1,183 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.7.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
|
|||
/** |
|||
* Array storage engine for cache. |
|||
* |
|||
* Not actually a persistent cache engine. All data is only |
|||
* stored in memory for the duration of a single process. While not |
|||
* useful in production settings this engine can be useful in tests |
|||
* or console tools where you don't want the overhead of interacting |
|||
* with a cache servers, but want the work saving properties a cache |
|||
* provides. |
|||
*/ |
|||
class ArrayEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* Cached data. |
|||
* |
|||
* Structured as [key => [exp => expiration, val => value]] |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $data = []; |
|||
|
|||
/** |
|||
* Write data for key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
$expires = time() + $this->_config['duration']; |
|||
$this->data[$key] = ['exp' => $expires, 'val' => $value]; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, |
|||
* has expired, or if there was an error fetching it |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
if (!isset($this->data[$key])) { |
|||
return false; |
|||
} |
|||
$data = $this->data[$key]; |
|||
|
|||
// Check expiration |
|||
$now = time(); |
|||
if ($data['exp'] <= $now) { |
|||
unset($this->data[$key]); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
return $data['val']; |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
if (!$this->read($key)) { |
|||
$this->write($key, 0); |
|||
} |
|||
$key = $this->_key($key); |
|||
$this->data[$key]['val'] += $offset; |
|||
|
|||
return $this->data[$key]['val']; |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
if (!$this->read($key)) { |
|||
$this->write($key, 0); |
|||
} |
|||
$key = $this->_key($key); |
|||
$this->data[$key]['val'] -= $offset; |
|||
|
|||
return $this->data[$key]['val']; |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
unset($this->data[$key]); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache. This will clear every cache config using APC. |
|||
* |
|||
* @param bool $check Unused argument required by interface. |
|||
* @return bool True Returns true. |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
$this->data = []; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
$result = []; |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$key = $this->_config['prefix'] . $group; |
|||
if (!isset($this->data[$key])) { |
|||
$this->data[$key] = ['exp' => PHP_INT_MAX, 'val' => 1]; |
|||
} |
|||
$value = $this->data[$key]['val']; |
|||
$result[] = $group . $value; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group The group to clear. |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
$key = $this->_config['prefix'] . $group; |
|||
if (isset($this->data[$key])) { |
|||
$this->data[$key]['val'] += 1; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,520 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 1.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
use Cake\Utility\Inflector; |
|||
use CallbackFilterIterator; |
|||
use Exception; |
|||
use LogicException; |
|||
use RecursiveDirectoryIterator; |
|||
use RecursiveIteratorIterator; |
|||
use SplFileInfo; |
|||
use SplFileObject; |
|||
|
|||
/** |
|||
* File Storage engine for cache. Filestorage is the slowest cache storage |
|||
* to read and write. However, it is good for servers that don't have other storage |
|||
* engine available, or have content which is not performance sensitive. |
|||
* |
|||
* You can configure a FileEngine cache, using Cache::config() |
|||
*/ |
|||
class FileEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* Instance of SplFileObject class |
|||
* |
|||
* @var \SplFileObject|null |
|||
*/ |
|||
protected $_File; |
|||
|
|||
/** |
|||
* The default config used unless overridden by runtime configuration |
|||
* |
|||
* - `duration` Specify how long items in this cache configuration last. |
|||
* - `groups` List of groups or 'tags' associated to every key stored in this config. |
|||
* handy for deleting a complete group from cache. |
|||
* - `isWindows` Automatically populated with whether the host is windows or not |
|||
* - `lock` Used by FileCache. Should files be locked before writing to them? |
|||
* - `mask` The mask used for created files |
|||
* - `path` Path to where cachefiles should be saved. Defaults to system's temp dir. |
|||
* - `prefix` Prepended to all entries. Good for when you need to share a keyspace |
|||
* with either another cache config or another application. |
|||
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable |
|||
* cache::gc from ever being called automatically. |
|||
* - `serialize` Should cache objects be serialized first. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_defaultConfig = [ |
|||
'duration' => 3600, |
|||
'groups' => [], |
|||
'isWindows' => false, |
|||
'lock' => true, |
|||
'mask' => 0664, |
|||
'path' => null, |
|||
'prefix' => 'cake_', |
|||
'probability' => 100, |
|||
'serialize' => true, |
|||
]; |
|||
|
|||
/** |
|||
* True unless FileEngine::__active(); fails |
|||
* |
|||
* @var bool |
|||
*/ |
|||
protected $_init = true; |
|||
|
|||
/** |
|||
* Initialize File Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend. |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
parent::init($config); |
|||
|
|||
if ($this->_config['path'] === null) { |
|||
$this->_config['path'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cake_cache' . DIRECTORY_SEPARATOR; |
|||
} |
|||
if (DIRECTORY_SEPARATOR === '\\') { |
|||
$this->_config['isWindows'] = true; |
|||
} |
|||
if (substr($this->_config['path'], -1) !== DIRECTORY_SEPARATOR) { |
|||
$this->_config['path'] .= DIRECTORY_SEPARATOR; |
|||
} |
|||
if ($this->_groupPrefix) { |
|||
$this->_groupPrefix = str_replace('_', DIRECTORY_SEPARATOR, $this->_groupPrefix); |
|||
} |
|||
|
|||
return $this->_active(); |
|||
} |
|||
|
|||
/** |
|||
* Garbage collection. Permanently remove all expired and deleted data |
|||
* |
|||
* @param int|null $expires [optional] An expires timestamp, invalidating all data before. |
|||
* @return bool True if garbage collection was successful, false on failure |
|||
*/ |
|||
public function gc($expires = null) |
|||
{ |
|||
return $this->clear(true); |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $data Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public function write($key, $data) |
|||
{ |
|||
if ($data === '' || !$this->_init) { |
|||
return false; |
|||
} |
|||
|
|||
$key = $this->_key($key); |
|||
|
|||
if ($this->_setKey($key, true) === false) { |
|||
return false; |
|||
} |
|||
|
|||
$lineBreak = "\n"; |
|||
|
|||
if ($this->_config['isWindows']) { |
|||
$lineBreak = "\r\n"; |
|||
} |
|||
|
|||
if (!empty($this->_config['serialize'])) { |
|||
if ($this->_config['isWindows']) { |
|||
$data = str_replace('\\', '\\\\\\\\', serialize($data)); |
|||
} else { |
|||
$data = serialize($data); |
|||
} |
|||
} |
|||
|
|||
$duration = $this->_config['duration']; |
|||
$expires = time() + $duration; |
|||
$contents = implode([$expires, $lineBreak, $data, $lineBreak]); |
|||
|
|||
if ($this->_config['lock']) { |
|||
$this->_File->flock(LOCK_EX); |
|||
} |
|||
|
|||
$this->_File->rewind(); |
|||
$success = $this->_File->ftruncate(0) && |
|||
$this->_File->fwrite($contents) && |
|||
$this->_File->fflush(); |
|||
|
|||
if ($this->_config['lock']) { |
|||
$this->_File->flock(LOCK_UN); |
|||
} |
|||
$this->_File = null; |
|||
|
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, has |
|||
* expired, or if there was an error fetching it |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
if (!$this->_init || $this->_setKey($key) === false) { |
|||
return false; |
|||
} |
|||
|
|||
if ($this->_config['lock']) { |
|||
$this->_File->flock(LOCK_SH); |
|||
} |
|||
|
|||
$this->_File->rewind(); |
|||
$time = time(); |
|||
$cachetime = (int)$this->_File->current(); |
|||
|
|||
if ($cachetime < $time) { |
|||
if ($this->_config['lock']) { |
|||
$this->_File->flock(LOCK_UN); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
$data = ''; |
|||
$this->_File->next(); |
|||
while ($this->_File->valid()) { |
|||
$data .= $this->_File->current(); |
|||
$this->_File->next(); |
|||
} |
|||
|
|||
if ($this->_config['lock']) { |
|||
$this->_File->flock(LOCK_UN); |
|||
} |
|||
|
|||
$data = trim($data); |
|||
|
|||
if ($data !== '' && !empty($this->_config['serialize'])) { |
|||
if ($this->_config['isWindows']) { |
|||
$data = str_replace('\\\\\\\\', '\\', $data); |
|||
} |
|||
$data = unserialize((string)$data); |
|||
} |
|||
|
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't |
|||
* exist or couldn't be removed |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
if ($this->_setKey($key) === false || !$this->_init) { |
|||
return false; |
|||
} |
|||
|
|||
$path = $this->_File->getRealPath(); |
|||
$this->_File = null; |
|||
|
|||
//@codingStandardsIgnoreStart |
|||
return @unlink($path); |
|||
//@codingStandardsIgnoreEnd |
|||
} |
|||
|
|||
/** |
|||
* Delete all values from the cache |
|||
* |
|||
* @param bool $check Optional - only delete expired cache items |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
if (!$this->_init) { |
|||
return false; |
|||
} |
|||
$this->_File = null; |
|||
|
|||
$threshold = $now = false; |
|||
if ($check) { |
|||
$now = time(); |
|||
$threshold = $now - $this->_config['duration']; |
|||
} |
|||
|
|||
$this->_clearDirectory($this->_config['path'], $now, $threshold); |
|||
|
|||
$directory = new RecursiveDirectoryIterator( |
|||
$this->_config['path'], |
|||
\FilesystemIterator::SKIP_DOTS |
|||
); |
|||
$contents = new RecursiveIteratorIterator( |
|||
$directory, |
|||
RecursiveIteratorIterator::SELF_FIRST |
|||
); |
|||
$cleared = []; |
|||
foreach ($contents as $path) { |
|||
if ($path->isFile()) { |
|||
continue; |
|||
} |
|||
|
|||
$path = $path->getRealPath() . DIRECTORY_SEPARATOR; |
|||
if (!in_array($path, $cleared, true)) { |
|||
$this->_clearDirectory($path, $now, $threshold); |
|||
$cleared[] = $path; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Used to clear a directory of matching files. |
|||
* |
|||
* @param string $path The path to search. |
|||
* @param int $now The current timestamp |
|||
* @param int $threshold Any file not modified after this value will be deleted. |
|||
* @return void |
|||
*/ |
|||
protected function _clearDirectory($path, $now, $threshold) |
|||
{ |
|||
if (!is_dir($path)) { |
|||
return; |
|||
} |
|||
$prefixLength = strlen($this->_config['prefix']); |
|||
|
|||
$dir = dir($path); |
|||
while (($entry = $dir->read()) !== false) { |
|||
if (substr($entry, 0, $prefixLength) !== $this->_config['prefix']) { |
|||
continue; |
|||
} |
|||
|
|||
try { |
|||
$file = new SplFileObject($path . $entry, 'r'); |
|||
} catch (Exception $e) { |
|||
continue; |
|||
} |
|||
|
|||
if ($threshold) { |
|||
$mtime = $file->getMTime(); |
|||
if ($mtime > $threshold) { |
|||
continue; |
|||
} |
|||
|
|||
$expires = (int)$file->current(); |
|||
if ($expires > $now) { |
|||
continue; |
|||
} |
|||
} |
|||
if ($file->isFile()) { |
|||
$filePath = $file->getRealPath(); |
|||
$file = null; |
|||
|
|||
//@codingStandardsIgnoreStart |
|||
@unlink($filePath); |
|||
//@codingStandardsIgnoreEnd |
|||
} |
|||
} |
|||
|
|||
$dir->close(); |
|||
} |
|||
|
|||
/** |
|||
* Not implemented |
|||
* |
|||
* @param string $key The key to decrement |
|||
* @param int $offset The number to offset |
|||
* @return void |
|||
* @throws \LogicException |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
throw new LogicException('Files cannot be atomically decremented.'); |
|||
} |
|||
|
|||
/** |
|||
* Not implemented |
|||
* |
|||
* @param string $key The key to increment |
|||
* @param int $offset The number to offset |
|||
* @return void |
|||
* @throws \LogicException |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
throw new LogicException('Files cannot be atomically incremented.'); |
|||
} |
|||
|
|||
/** |
|||
* Sets the current cache key this class is managing, and creates a writable SplFileObject |
|||
* for the cache file the key is referring to. |
|||
* |
|||
* @param string $key The key |
|||
* @param bool $createKey Whether the key should be created if it doesn't exists, or not |
|||
* @return bool true if the cache key could be set, false otherwise |
|||
*/ |
|||
protected function _setKey($key, $createKey = false) |
|||
{ |
|||
$groups = null; |
|||
if ($this->_groupPrefix) { |
|||
$groups = vsprintf($this->_groupPrefix, $this->groups()); |
|||
} |
|||
$dir = $this->_config['path'] . $groups; |
|||
|
|||
if (!is_dir($dir)) { |
|||
mkdir($dir, 0775, true); |
|||
} |
|||
|
|||
$path = new SplFileInfo($dir . $key); |
|||
|
|||
if (!$createKey && !$path->isFile()) { |
|||
return false; |
|||
} |
|||
if ( |
|||
empty($this->_File) || |
|||
$this->_File->getBasename() !== $key || |
|||
$this->_File->valid() === false |
|||
) { |
|||
$exists = file_exists($path->getPathname()); |
|||
try { |
|||
$this->_File = $path->openFile('c+'); |
|||
} catch (Exception $e) { |
|||
trigger_error($e->getMessage(), E_USER_WARNING); |
|||
|
|||
return false; |
|||
} |
|||
unset($path); |
|||
|
|||
if (!$exists && !chmod($this->_File->getPathname(), (int)$this->_config['mask'])) { |
|||
trigger_error(sprintf( |
|||
'Could not apply permission mask "%s" on cache file "%s"', |
|||
$this->_File->getPathname(), |
|||
$this->_config['mask'] |
|||
), E_USER_WARNING); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Determine if cache directory is writable |
|||
* |
|||
* @return bool |
|||
*/ |
|||
protected function _active() |
|||
{ |
|||
$dir = new SplFileInfo($this->_config['path']); |
|||
$path = $dir->getPathname(); |
|||
$success = true; |
|||
if (!is_dir($path)) { |
|||
//@codingStandardsIgnoreStart |
|||
$success = @mkdir($path, 0775, true); |
|||
//@codingStandardsIgnoreEnd |
|||
} |
|||
|
|||
$isWritableDir = ($dir->isDir() && $dir->isWritable()); |
|||
if (!$success || ($this->_init && !$isWritableDir)) { |
|||
$this->_init = false; |
|||
trigger_error(sprintf( |
|||
'%s is not writable', |
|||
$this->_config['path'] |
|||
), E_USER_WARNING); |
|||
} |
|||
|
|||
return $success; |
|||
} |
|||
|
|||
/** |
|||
* Generates a safe key for use with cache engine storage engines. |
|||
* |
|||
* @param string $key the key passed over |
|||
* @return mixed string $key or false |
|||
*/ |
|||
public function key($key) |
|||
{ |
|||
if (empty($key)) { |
|||
return false; |
|||
} |
|||
|
|||
$key = Inflector::underscore(str_replace( |
|||
[DIRECTORY_SEPARATOR, '/', '.', '<', '>', '?', ':', '|', '*', '"'], |
|||
'_', |
|||
(string)$key |
|||
)); |
|||
|
|||
return $key; |
|||
} |
|||
|
|||
/** |
|||
* Recursively deletes all files under any directory named as $group |
|||
* |
|||
* @param string $group The group to clear. |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
$this->_File = null; |
|||
|
|||
$prefix = (string)$this->_config['prefix']; |
|||
|
|||
$directoryIterator = new RecursiveDirectoryIterator($this->_config['path']); |
|||
$contents = new RecursiveIteratorIterator( |
|||
$directoryIterator, |
|||
RecursiveIteratorIterator::CHILD_FIRST |
|||
); |
|||
$filtered = new CallbackFilterIterator( |
|||
$contents, |
|||
function (SplFileInfo $current) use ($group, $prefix) { |
|||
if (!$current->isFile()) { |
|||
return false; |
|||
} |
|||
|
|||
$hasPrefix = $prefix === '' |
|||
|| strpos($current->getBasename(), $prefix) === 0; |
|||
if ($hasPrefix === false) { |
|||
return false; |
|||
} |
|||
|
|||
$pos = strpos( |
|||
$current->getPathname(), |
|||
DIRECTORY_SEPARATOR . $group . DIRECTORY_SEPARATOR |
|||
); |
|||
|
|||
return $pos !== false; |
|||
} |
|||
); |
|||
foreach ($filtered as $object) { |
|||
$path = $object->getPathname(); |
|||
$object = null; |
|||
// @codingStandardsIgnoreLine |
|||
@unlink($path); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,534 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 2.5.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
use InvalidArgumentException; |
|||
use Memcached; |
|||
|
|||
/** |
|||
* Memcached storage engine for cache. Memcached has some limitations in the amount of |
|||
* control you have over expire times far in the future. See MemcachedEngine::write() for |
|||
* more information. |
|||
* |
|||
* Memcached engine supports binary protocol and igbinary |
|||
* serialization (if memcached extension is compiled with --enable-igbinary). |
|||
* Compressed keys can also be incremented/decremented. |
|||
*/ |
|||
class MemcachedEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* memcached wrapper. |
|||
* |
|||
* @var \Memcached |
|||
*/ |
|||
protected $_Memcached; |
|||
|
|||
/** |
|||
* The default config used unless overridden by runtime configuration |
|||
* |
|||
* - `compress` Whether to compress data |
|||
* - `duration` Specify how long items in this cache configuration last. |
|||
* - `groups` List of groups or 'tags' associated to every key stored in this config. |
|||
* handy for deleting a complete group from cache. |
|||
* - `username` Login to access the Memcache server |
|||
* - `password` Password to access the Memcache server |
|||
* - `persistent` The name of the persistent connection. All configurations using |
|||
* the same persistent value will share a single underlying connection. |
|||
* - `prefix` Prepended to all entries. Good for when you need to share a keyspace |
|||
* with either another cache config or another application. |
|||
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable |
|||
* cache::gc from ever being called automatically. |
|||
* - `serialize` The serializer engine used to serialize data. Available engines are php, |
|||
* igbinary and json. Beside php, the memcached extension must be compiled with the |
|||
* appropriate serializer support. |
|||
* - `servers` String or array of memcached servers. If an array MemcacheEngine will use |
|||
* them as a pool. |
|||
* - `options` - Additional options for the memcached client. Should be an array of option => value. |
|||
* Use the \Memcached::OPT_* constants as keys. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_defaultConfig = [ |
|||
'compress' => false, |
|||
'duration' => 3600, |
|||
'groups' => [], |
|||
'host' => null, |
|||
'username' => null, |
|||
'password' => null, |
|||
'persistent' => false, |
|||
'port' => null, |
|||
'prefix' => 'cake_', |
|||
'probability' => 100, |
|||
'serialize' => 'php', |
|||
'servers' => ['127.0.0.1'], |
|||
'options' => [], |
|||
]; |
|||
|
|||
/** |
|||
* List of available serializer engines |
|||
* |
|||
* Memcached must be compiled with json and igbinary support to use these engines |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_serializers = []; |
|||
|
|||
/** |
|||
* @var string[] |
|||
*/ |
|||
protected $_compiledGroupNames = []; |
|||
|
|||
/** |
|||
* Initialize the Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
* @throws \InvalidArgumentException When you try use authentication without |
|||
* Memcached compiled with SASL support |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
if (!extension_loaded('memcached')) { |
|||
return false; |
|||
} |
|||
|
|||
$this->_serializers = [ |
|||
'igbinary' => Memcached::SERIALIZER_IGBINARY, |
|||
'json' => Memcached::SERIALIZER_JSON, |
|||
'php' => Memcached::SERIALIZER_PHP, |
|||
]; |
|||
if (defined('Memcached::HAVE_MSGPACK') && Memcached::HAVE_MSGPACK) { |
|||
$this->_serializers['msgpack'] = Memcached::SERIALIZER_MSGPACK; |
|||
} |
|||
|
|||
parent::init($config); |
|||
|
|||
if (!empty($config['host'])) { |
|||
if (empty($config['port'])) { |
|||
$config['servers'] = [$config['host']]; |
|||
} else { |
|||
$config['servers'] = [sprintf('%s:%d', $config['host'], $config['port'])]; |
|||
} |
|||
} |
|||
|
|||
if (isset($config['servers'])) { |
|||
$this->setConfig('servers', $config['servers'], false); |
|||
} |
|||
|
|||
if (!is_array($this->_config['servers'])) { |
|||
$this->_config['servers'] = [$this->_config['servers']]; |
|||
} |
|||
|
|||
if (isset($this->_Memcached)) { |
|||
return true; |
|||
} |
|||
|
|||
if ($this->_config['persistent']) { |
|||
$this->_Memcached = new Memcached((string)$this->_config['persistent']); |
|||
} else { |
|||
$this->_Memcached = new Memcached(); |
|||
} |
|||
$this->_setOptions(); |
|||
|
|||
if (count($this->_Memcached->getServerList())) { |
|||
return true; |
|||
} |
|||
|
|||
$servers = []; |
|||
foreach ($this->_config['servers'] as $server) { |
|||
$servers[] = $this->parseServerString($server); |
|||
} |
|||
|
|||
if (!$this->_Memcached->addServers($servers)) { |
|||
return false; |
|||
} |
|||
|
|||
if (is_array($this->_config['options'])) { |
|||
foreach ($this->_config['options'] as $opt => $value) { |
|||
$this->_Memcached->setOption($opt, $value); |
|||
} |
|||
} |
|||
|
|||
if (empty($this->_config['username']) && !empty($this->_config['login'])) { |
|||
throw new InvalidArgumentException( |
|||
'Please pass "username" instead of "login" for connecting to Memcached' |
|||
); |
|||
} |
|||
|
|||
if ($this->_config['username'] !== null && $this->_config['password'] !== null) { |
|||
if (!method_exists($this->_Memcached, 'setSaslAuthData')) { |
|||
throw new InvalidArgumentException( |
|||
'Memcached extension is not built with SASL support' |
|||
); |
|||
} |
|||
$this->_Memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, true); |
|||
$this->_Memcached->setSaslAuthData( |
|||
$this->_config['username'], |
|||
$this->_config['password'] |
|||
); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Settings the memcached instance |
|||
* |
|||
* @return void |
|||
* @throws \InvalidArgumentException When the Memcached extension is not built |
|||
* with the desired serializer engine. |
|||
*/ |
|||
protected function _setOptions() |
|||
{ |
|||
$this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true); |
|||
|
|||
$serializer = strtolower($this->_config['serialize']); |
|||
if (!isset($this->_serializers[$serializer])) { |
|||
throw new InvalidArgumentException( |
|||
sprintf('%s is not a valid serializer engine for Memcached', $serializer) |
|||
); |
|||
} |
|||
|
|||
if ( |
|||
$serializer !== 'php' && |
|||
!constant('Memcached::HAVE_' . strtoupper($serializer)) |
|||
) { |
|||
throw new InvalidArgumentException( |
|||
sprintf('Memcached extension is not compiled with %s support', $serializer) |
|||
); |
|||
} |
|||
|
|||
$this->_Memcached->setOption( |
|||
Memcached::OPT_SERIALIZER, |
|||
$this->_serializers[$serializer] |
|||
); |
|||
|
|||
// Check for Amazon ElastiCache instance |
|||
if ( |
|||
defined('Memcached::OPT_CLIENT_MODE') && |
|||
defined('Memcached::DYNAMIC_CLIENT_MODE') |
|||
) { |
|||
$this->_Memcached->setOption( |
|||
Memcached::OPT_CLIENT_MODE, |
|||
Memcached::DYNAMIC_CLIENT_MODE |
|||
); |
|||
} |
|||
|
|||
$this->_Memcached->setOption( |
|||
Memcached::OPT_COMPRESSION, |
|||
(bool)$this->_config['compress'] |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Parses the server address into the host/port. Handles both IPv6 and IPv4 |
|||
* addresses and Unix sockets |
|||
* |
|||
* @param string $server The server address string. |
|||
* @return array Array containing host, port |
|||
*/ |
|||
public function parseServerString($server) |
|||
{ |
|||
$socketTransport = 'unix://'; |
|||
if (strpos($server, $socketTransport) === 0) { |
|||
return [substr($server, strlen($socketTransport)), 0]; |
|||
} |
|||
if (substr($server, 0, 1) === '[') { |
|||
$position = strpos($server, ']:'); |
|||
if ($position !== false) { |
|||
$position++; |
|||
} |
|||
} else { |
|||
$position = strpos($server, ':'); |
|||
} |
|||
$port = 11211; |
|||
$host = $server; |
|||
if ($position !== false) { |
|||
$host = substr($server, 0, $position); |
|||
$port = substr($server, $position + 1); |
|||
} |
|||
|
|||
return [$host, (int)$port]; |
|||
} |
|||
|
|||
/** |
|||
* Backwards compatible alias of parseServerString |
|||
* |
|||
* @param string $server The server address string. |
|||
* @return array Array containing host, port |
|||
* @deprecated 3.4.13 Will be removed in 4.0.0 |
|||
*/ |
|||
protected function _parseServerString($server) |
|||
{ |
|||
return $this->parseServerString($server); |
|||
} |
|||
|
|||
/** |
|||
* Read an option value from the memcached connection. |
|||
* |
|||
* @param string $name The option name to read. |
|||
* @return string|int|bool|null |
|||
*/ |
|||
public function getOption($name) |
|||
{ |
|||
return $this->_Memcached->getOption($name); |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache. When using memcached as your cache engine |
|||
* remember that the Memcached pecl extension does not support cache expiry |
|||
* times greater than 30 days in the future. Any duration greater than 30 days |
|||
* will be treated as never expiring. |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
* @see https://secure.php.net/manual/en/memcache.set.php |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$duration = $this->_config['duration']; |
|||
if ($duration > 30 * DAY) { |
|||
$duration = 0; |
|||
} |
|||
|
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->set($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Write many cache entries to the cache at once |
|||
* |
|||
* @param array $data An array of data to be stored in the cache |
|||
* @return array of bools for each key provided, true if the data was |
|||
* successfully cached, false on failure |
|||
*/ |
|||
public function writeMany($data) |
|||
{ |
|||
$cacheData = []; |
|||
foreach ($data as $key => $value) { |
|||
$cacheData[$this->_key($key)] = $value; |
|||
} |
|||
|
|||
$success = $this->_Memcached->setMulti($cacheData); |
|||
|
|||
$return = []; |
|||
foreach (array_keys($data) as $key) { |
|||
$return[$key] = $success; |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, has |
|||
* expired, or if there was an error fetching it. |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->get($key); |
|||
} |
|||
|
|||
/** |
|||
* Read many keys from the cache at once |
|||
* |
|||
* @param array $keys An array of identifiers for the data |
|||
* @return array An array containing, for each of the given $keys, the cached data or |
|||
* false if cached data could not be retrieved. |
|||
*/ |
|||
public function readMany($keys) |
|||
{ |
|||
$cacheKeys = []; |
|||
foreach ($keys as $key) { |
|||
$cacheKeys[] = $this->_key($key); |
|||
} |
|||
|
|||
$values = $this->_Memcached->getMulti($cacheKeys); |
|||
$return = []; |
|||
foreach ($keys as &$key) { |
|||
$return[$key] = array_key_exists($this->_key($key), $values) ? |
|||
$values[$this->_key($key)] : false; |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->increment($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->decrement($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't |
|||
* exist or couldn't be removed. |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->delete($key); |
|||
} |
|||
|
|||
/** |
|||
* Delete many keys from the cache at once |
|||
* |
|||
* @param array $keys An array of identifiers for the data |
|||
* @return array of boolean values that are true if the key was successfully |
|||
* deleted, false if it didn't exist or couldn't be removed. |
|||
*/ |
|||
public function deleteMany($keys) |
|||
{ |
|||
$cacheKeys = []; |
|||
foreach ($keys as $key) { |
|||
$cacheKeys[] = $this->_key($key); |
|||
} |
|||
|
|||
$success = $this->_Memcached->deleteMulti($cacheKeys); |
|||
|
|||
$return = []; |
|||
foreach ($keys as $key) { |
|||
$return[$key] = $success; |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache |
|||
* |
|||
* @param bool $check If true will check expiration, otherwise delete all. |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
if ($check) { |
|||
return true; |
|||
} |
|||
|
|||
$keys = $this->_Memcached->getAllKeys(); |
|||
if ($keys === false) { |
|||
return false; |
|||
} |
|||
|
|||
foreach ($keys as $key) { |
|||
if (strpos($key, $this->_config['prefix']) === 0) { |
|||
$this->_Memcached->delete($key); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Add a key to the cache if it does not already exist. |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
*/ |
|||
public function add($key, $value) |
|||
{ |
|||
$duration = $this->_config['duration']; |
|||
if ($duration > 30 * DAY) { |
|||
$duration = 0; |
|||
} |
|||
|
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Memcached->add($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
if (empty($this->_compiledGroupNames)) { |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group; |
|||
} |
|||
} |
|||
|
|||
$groups = $this->_Memcached->getMulti($this->_compiledGroupNames) ?: []; |
|||
if (count($groups) !== count($this->_config['groups'])) { |
|||
foreach ($this->_compiledGroupNames as $group) { |
|||
if (!isset($groups[$group])) { |
|||
$this->_Memcached->set($group, 1, 0); |
|||
$groups[$group] = 1; |
|||
} |
|||
} |
|||
ksort($groups); |
|||
} |
|||
|
|||
$result = []; |
|||
$groups = array_values($groups); |
|||
foreach ($this->_config['groups'] as $i => $group) { |
|||
$result[] = $group . $groups[$i]; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group name of the group to be cleared |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return (bool)$this->_Memcached->increment($this->_config['prefix'] . $group); |
|||
} |
|||
} |
@ -0,0 +1,112 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.0.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
|
|||
/** |
|||
* Null cache engine, all operations appear to work, but do nothing. |
|||
* |
|||
* This is used internally for when Cache::disable() has been called. |
|||
*/ |
|||
class NullEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function gc($expires = null) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function readMany($keys) |
|||
{ |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function deleteMany($keys) |
|||
{ |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,321 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 2.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
|
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
use Redis; |
|||
use RedisException; |
|||
|
|||
/** |
|||
* Redis storage engine for cache. |
|||
*/ |
|||
class RedisEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* Redis wrapper. |
|||
* |
|||
* @var \Redis |
|||
*/ |
|||
protected $_Redis; |
|||
|
|||
/** |
|||
* The default config used unless overridden by runtime configuration |
|||
* |
|||
* - `database` database number to use for connection. |
|||
* - `duration` Specify how long items in this cache configuration last. |
|||
* - `groups` List of groups or 'tags' associated to every key stored in this config. |
|||
* handy for deleting a complete group from cache. |
|||
* - `password` Redis server password. |
|||
* - `persistent` Connect to the Redis server with a persistent connection |
|||
* - `port` port number to the Redis server. |
|||
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace |
|||
* with either another cache config or another application. |
|||
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable |
|||
* cache::gc from ever being called automatically. |
|||
* - `server` URL or ip to the Redis server host. |
|||
* - `timeout` timeout in seconds (float). |
|||
* - `unix_socket` Path to the unix socket file (default: false) |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_defaultConfig = [ |
|||
'database' => 0, |
|||
'duration' => 3600, |
|||
'groups' => [], |
|||
'password' => false, |
|||
'persistent' => true, |
|||
'port' => 6379, |
|||
'prefix' => 'cake_', |
|||
'probability' => 100, |
|||
'host' => null, |
|||
'server' => '127.0.0.1', |
|||
'timeout' => 0, |
|||
'unix_socket' => false, |
|||
]; |
|||
|
|||
/** |
|||
* Initialize the Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
if (!extension_loaded('redis')) { |
|||
return false; |
|||
} |
|||
|
|||
if (!empty($config['host'])) { |
|||
$config['server'] = $config['host']; |
|||
} |
|||
|
|||
parent::init($config); |
|||
|
|||
return $this->_connect(); |
|||
} |
|||
|
|||
/** |
|||
* Connects to a Redis server |
|||
* |
|||
* @return bool True if Redis server was connected |
|||
*/ |
|||
protected function _connect() |
|||
{ |
|||
try { |
|||
$this->_Redis = new Redis(); |
|||
if (!empty($this->_config['unix_socket'])) { |
|||
$return = $this->_Redis->connect($this->_config['unix_socket']); |
|||
} elseif (empty($this->_config['persistent'])) { |
|||
$return = $this->_Redis->connect($this->_config['server'], $this->_config['port'], $this->_config['timeout']); |
|||
} else { |
|||
$persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database']; |
|||
$return = $this->_Redis->pconnect($this->_config['server'], $this->_config['port'], $this->_config['timeout'], $persistentId); |
|||
} |
|||
} catch (RedisException $e) { |
|||
return false; |
|||
} |
|||
if ($return && $this->_config['password']) { |
|||
$return = $this->_Redis->auth($this->_config['password']); |
|||
} |
|||
if ($return) { |
|||
$return = $this->_Redis->select($this->_config['database']); |
|||
} |
|||
|
|||
return $return; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache. |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
if (!is_int($value)) { |
|||
$value = serialize($value); |
|||
} |
|||
|
|||
$duration = $this->_config['duration']; |
|||
if ($duration === 0) { |
|||
return $this->_Redis->set($key, $value); |
|||
} |
|||
|
|||
return $this->_Redis->setEx($key, $duration, $value); |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
$value = $this->_Redis->get($key); |
|||
if (preg_match('/^[-]?\d+$/', $value)) { |
|||
return (int)$value; |
|||
} |
|||
if ($value !== false && is_string($value)) { |
|||
return unserialize($value); |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key & update the expiry time |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
$duration = $this->_config['duration']; |
|||
$key = $this->_key($key); |
|||
|
|||
$value = (int)$this->_Redis->incrBy($key, $offset); |
|||
if ($duration > 0) { |
|||
$this->_Redis->expire($key, $duration); |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key & update the expiry time |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
$duration = $this->_config['duration']; |
|||
$key = $this->_key($key); |
|||
|
|||
$value = (int)$this->_Redis->decrBy($key, $offset); |
|||
if ($duration > 0) { |
|||
$this->_Redis->expire($key, $duration); |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return $this->_Redis->del($key) > 0; |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache |
|||
* |
|||
* @param bool $check If true will check expiration, otherwise delete all. |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
if ($check) { |
|||
return true; |
|||
} |
|||
|
|||
$this->_Redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); |
|||
|
|||
$isAllDeleted = true; |
|||
$iterator = null; |
|||
$pattern = $this->_config['prefix'] . '*'; |
|||
|
|||
while (true) { |
|||
$keys = $this->_Redis->scan($iterator, $pattern); |
|||
|
|||
if ($keys === false) { |
|||
break; |
|||
} |
|||
|
|||
foreach ($keys as $key) { |
|||
$isDeleted = ($this->_Redis->del($key) > 0); |
|||
$isAllDeleted = $isAllDeleted && $isDeleted; |
|||
} |
|||
} |
|||
|
|||
return $isAllDeleted; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache if it doesn't exist already. |
|||
* If it already exists, it fails and returns false. |
|||
* |
|||
* @param string $key Identifier for the data. |
|||
* @param mixed $value Data to be cached. |
|||
* @return bool True if the data was successfully cached, false on failure. |
|||
* @link https://github.com/phpredis/phpredis#set |
|||
*/ |
|||
public function add($key, $value) |
|||
{ |
|||
$duration = $this->_config['duration']; |
|||
$key = $this->_key($key); |
|||
|
|||
if (!is_int($value)) { |
|||
$value = serialize($value); |
|||
} |
|||
|
|||
if ($this->_Redis->set($key, $value, ['nx', 'ex' => $duration])) { |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
$result = []; |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$value = $this->_Redis->get($this->_config['prefix'] . $group); |
|||
if (!$value) { |
|||
$value = 1; |
|||
$this->_Redis->set($this->_config['prefix'] . $group, $value); |
|||
} |
|||
$result[] = $group . $value; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group name of the group to be cleared |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return (bool)$this->_Redis->incr($this->_config['prefix'] . $group); |
|||
} |
|||
|
|||
/** |
|||
* Disconnects from the redis server |
|||
*/ |
|||
public function __destruct() |
|||
{ |
|||
if (empty($this->_config['persistent']) && $this->_Redis instanceof Redis) { |
|||
$this->_Redis->close(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,197 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 2.0.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
|
|||
/** |
|||
* Wincache storage engine for cache |
|||
* |
|||
* Supports wincache 1.1.0 and higher. |
|||
*/ |
|||
class WincacheEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* Contains the compiled group names |
|||
* (prefixed with the global configuration prefix) |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_compiledGroupNames = []; |
|||
|
|||
/** |
|||
* Initialize the Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
if (!extension_loaded('wincache')) { |
|||
return false; |
|||
} |
|||
|
|||
parent::init($config); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
$duration = $this->_config['duration']; |
|||
|
|||
return wincache_ucache_set($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, |
|||
* has expired, or if there was an error fetching it |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return wincache_ucache_get($key); |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return wincache_ucache_inc($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return wincache_ucache_dec($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return wincache_ucache_delete($key); |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache. This will clear every |
|||
* item in the cache matching the cache config prefix. |
|||
* |
|||
* @param bool $check If true, nothing will be cleared, as entries will |
|||
* naturally expire in wincache.. |
|||
* @return bool True Returns true. |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
if ($check) { |
|||
return true; |
|||
} |
|||
$info = wincache_ucache_info(); |
|||
$cacheKeys = $info['ucache_entries']; |
|||
unset($info); |
|||
foreach ($cacheKeys as $key) { |
|||
if (strpos($key['key_name'], $this->_config['prefix']) === 0) { |
|||
wincache_ucache_delete($key['key_name']); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
if (empty($this->_compiledGroupNames)) { |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group; |
|||
} |
|||
} |
|||
|
|||
$groups = wincache_ucache_get($this->_compiledGroupNames); |
|||
if (count($groups) !== count($this->_config['groups'])) { |
|||
foreach ($this->_compiledGroupNames as $group) { |
|||
if (!isset($groups[$group])) { |
|||
wincache_ucache_set($group, 1); |
|||
$groups[$group] = 1; |
|||
} |
|||
} |
|||
ksort($groups); |
|||
} |
|||
|
|||
$result = []; |
|||
$groups = array_values($groups); |
|||
foreach ($this->_config['groups'] as $i => $group) { |
|||
$result[] = $group . $groups[$i]; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group The group to clear. |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
$success = false; |
|||
wincache_ucache_inc($this->_config['prefix'] . $group, 1, $success); |
|||
|
|||
return $success; |
|||
} |
|||
} |
@ -0,0 +1,255 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 1.2.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache\Engine; |
|||
|
|||
use Cake\Cache\CacheEngine; |
|||
|
|||
/** |
|||
* Xcache storage engine for cache |
|||
* |
|||
* @link http://trac.lighttpd.net/xcache/ Xcache |
|||
* @deprecated 3.6.0 Xcache engine has been deprecated and will be removed in 4.0.0. |
|||
*/ |
|||
class XcacheEngine extends CacheEngine |
|||
{ |
|||
/** |
|||
* The default config used unless overridden by runtime configuration |
|||
* |
|||
* - `duration` Specify how long items in this cache configuration last. |
|||
* - `groups` List of groups or 'tags' associated to every key stored in this config. |
|||
* handy for deleting a complete group from cache. |
|||
* - `prefix` Prefix appended to all entries. Good for when you need to share a keyspace |
|||
* with either another cache config or another application. |
|||
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable |
|||
* cache::gc from ever being called automatically. |
|||
* - `PHP_AUTH_USER` xcache.admin.user |
|||
* - `PHP_AUTH_PW` xcache.admin.password |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $_defaultConfig = [ |
|||
'duration' => 3600, |
|||
'groups' => [], |
|||
'prefix' => null, |
|||
'probability' => 100, |
|||
'PHP_AUTH_USER' => 'user', |
|||
'PHP_AUTH_PW' => 'password', |
|||
]; |
|||
|
|||
/** |
|||
* Initialize the Cache Engine |
|||
* |
|||
* Called automatically by the cache frontend |
|||
* |
|||
* @param array $config array of setting for the engine |
|||
* @return bool True if the engine has been successfully initialized, false if not |
|||
*/ |
|||
public function init(array $config = []) |
|||
{ |
|||
if (!extension_loaded('xcache')) { |
|||
return false; |
|||
} |
|||
|
|||
parent::init($config); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Write data for key into cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param mixed $value Data to be cached |
|||
* @return bool True if the data was successfully cached, false on failure |
|||
*/ |
|||
public function write($key, $value) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
if (!is_numeric($value)) { |
|||
$value = serialize($value); |
|||
} |
|||
|
|||
$duration = $this->_config['duration']; |
|||
$expires = time() + $duration; |
|||
xcache_set($key . '_expires', $expires, $duration); |
|||
|
|||
return xcache_set($key, $value, $duration); |
|||
} |
|||
|
|||
/** |
|||
* Read a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return mixed The cached data, or false if the data doesn't exist, |
|||
* has expired, or if there was an error fetching it |
|||
*/ |
|||
public function read($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
if (xcache_isset($key)) { |
|||
$time = time(); |
|||
$cachetime = (int)xcache_get($key . '_expires'); |
|||
if ($cachetime < $time || ($time + $this->_config['duration']) < $cachetime) { |
|||
return false; |
|||
} |
|||
|
|||
$value = xcache_get($key); |
|||
if (is_string($value) && !is_numeric($value)) { |
|||
$value = unserialize($value); |
|||
} |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Increments the value of an integer cached key |
|||
* If the cache key is not an integer it will be treated as 0 |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to increment |
|||
* @return int|false New incremented value, false otherwise |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return xcache_inc($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Decrements the value of an integer cached key. |
|||
* If the cache key is not an integer it will be treated as 0 |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @param int $offset How much to subtract |
|||
* @return int|false New decremented value, false otherwise |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return xcache_dec($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* Delete a key from the cache |
|||
* |
|||
* @param string $key Identifier for the data |
|||
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$key = $this->_key($key); |
|||
|
|||
return xcache_unset($key); |
|||
} |
|||
|
|||
/** |
|||
* Delete all keys from the cache |
|||
* |
|||
* @param bool $check If true no deletes will occur and instead CakePHP will rely |
|||
* on key TTL values. |
|||
* Unused for Xcache engine. |
|||
* @return bool True if the cache was successfully cleared, false otherwise |
|||
*/ |
|||
public function clear($check) |
|||
{ |
|||
$this->_auth(); |
|||
$max = xcache_count(XC_TYPE_VAR); |
|||
for ($i = 0; $i < $max; $i++) { |
|||
xcache_clear_cache(XC_TYPE_VAR, $i); |
|||
} |
|||
$this->_auth(true); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Returns the `group value` for each of the configured groups |
|||
* If the group initial value was not found, then it initializes |
|||
* the group accordingly. |
|||
* |
|||
* @return string[] |
|||
*/ |
|||
public function groups() |
|||
{ |
|||
$result = []; |
|||
foreach ($this->_config['groups'] as $group) { |
|||
$value = xcache_get($this->_config['prefix'] . $group); |
|||
if (!$value) { |
|||
$value = 1; |
|||
xcache_set($this->_config['prefix'] . $group, $value, 0); |
|||
} |
|||
$result[] = $group . $value; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Increments the group value to simulate deletion of all keys under a group |
|||
* old values will remain in storage until they expire. |
|||
* |
|||
* @param string $group The group to clear. |
|||
* @return bool success |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return (bool)xcache_inc($this->_config['prefix'] . $group, 1); |
|||
} |
|||
|
|||
/** |
|||
* Populates and reverses $_SERVER authentication values |
|||
* Makes necessary changes (and reverting them back) in $_SERVER |
|||
* |
|||
* This has to be done because xcache_clear_cache() needs to pass Basic Http Auth |
|||
* (see xcache.admin configuration config) |
|||
* |
|||
* @param bool $reverse Revert changes |
|||
* @return void |
|||
*/ |
|||
protected function _auth($reverse = false) |
|||
{ |
|||
static $backup = []; |
|||
$keys = ['PHP_AUTH_USER' => 'user', 'PHP_AUTH_PW' => 'password']; |
|||
foreach ($keys as $key => $value) { |
|||
if ($reverse) { |
|||
if (isset($backup[$key])) { |
|||
$_SERVER[$key] = $backup[$key]; |
|||
unset($backup[$key]); |
|||
} else { |
|||
unset($_SERVER[$key]); |
|||
} |
|||
} else { |
|||
$value = env($key); |
|||
if (!empty($value)) { |
|||
$backup[$key] = $value; |
|||
} |
|||
if (!empty($this->_config[$value])) { |
|||
$_SERVER[$key] = $this->_config[$value]; |
|||
} elseif (!empty($this->_config[$key])) { |
|||
$_SERVER[$key] = $this->_config[$key]; |
|||
} else { |
|||
$_SERVER[$key] = $value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.7.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
namespace Cake\Cache; |
|||
|
|||
use Cake\Core\Exception\Exception; |
|||
use Psr\SimpleCache\InvalidArgumentException as InvalidArgumentInterface; |
|||
|
|||
/** |
|||
* Exception raised when cache keys are invalid. |
|||
*/ |
|||
class InvalidArgumentException extends Exception implements InvalidArgumentInterface |
|||
{ |
|||
} |
@ -0,0 +1,57 @@ |
|||
# CakePHP Caching Library |
|||
|
|||
The Cache library provides a `Cache` service locator for interfacing with multiple caching backends using |
|||
a simple to use interface. |
|||
|
|||
The caching backends supported are: |
|||
|
|||
* Files |
|||
* APC |
|||
* Memcached |
|||
* Redis |
|||
* Wincache |
|||
* Xcache |
|||
|
|||
## Usage |
|||
|
|||
Caching engines need to be configured with the `Cache::config()` method. |
|||
|
|||
```php |
|||
use Cake\Cache\Cache; |
|||
|
|||
// Using a short name |
|||
Cache::config('default', [ |
|||
'className' => 'File', |
|||
'duration' => '+1 hours', |
|||
'path' => sys_get_tmp_dir(), |
|||
'prefix' => 'my_app_' |
|||
]); |
|||
|
|||
// Using a fully namespaced name. |
|||
Cache::config('long', [ |
|||
'className' => 'Cake\Cache\Engine\ApcuEngine', |
|||
'duration' => '+1 week', |
|||
'prefix' => 'my_app_' |
|||
]); |
|||
|
|||
// Using a constructed object. |
|||
$object = new FileEngine($config); |
|||
Cache::config('other', $object); |
|||
``` |
|||
|
|||
You can now read a write from the cache: |
|||
|
|||
```php |
|||
$data = Cache::remember('my_cache_key', function () { |
|||
return Service::expensiveCall(); |
|||
}); |
|||
``` |
|||
|
|||
The code above will try to look for data stored in cache under the `my_cache_key`, if not found |
|||
the callback will be executed and the returned data will be cached for future calls. |
|||
|
|||
## Documentation |
|||
|
|||
Please make sure you check the [official documentation](https://book.cakephp.org/3/en/core-libraries/caching.html) |
|||
|
|||
|
@ -0,0 +1,282 @@ |
|||
<?php |
|||
/** |
|||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
|||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* |
|||
* Licensed under The MIT License |
|||
* For full copyright and license information, please see the LICENSE.txt |
|||
* Redistributions of files must retain the above copyright notice. |
|||
* |
|||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
|||
* @link https://cakephp.org CakePHP(tm) Project |
|||
* @since 3.7.0 |
|||
* @license https://opensource.org/licenses/mit-license.php MIT License |
|||
*/ |
|||
|
|||
namespace Cake\Cache; |
|||
|
|||
use Cake\Cache\CacheEngineInterface; |
|||
use Psr\SimpleCache\CacheInterface; |
|||
|
|||
/** |
|||
* Wrapper for Cake engines that allow them to support |
|||
* the PSR16 Simple Cache Interface |
|||
* |
|||
* @since 3.7.0 |
|||
* @link https://www.php-fig.org/psr/psr-16/ |
|||
*/ |
|||
class SimpleCacheEngine implements CacheInterface, CacheEngineInterface |
|||
{ |
|||
/** |
|||
* The wrapped cache engine object. |
|||
* |
|||
* @var \Cake\Cache\CacheEngine |
|||
*/ |
|||
protected $innerEngine; |
|||
|
|||
/** |
|||
* Constructor |
|||
* |
|||
* @param \Cake\Cache\CacheEngine $innerEngine The decorated engine. |
|||
*/ |
|||
public function __construct(CacheEngine $innerEngine) |
|||
{ |
|||
$this->innerEngine = $innerEngine; |
|||
} |
|||
|
|||
/** |
|||
* Ensure the validity of the given cache key. |
|||
* |
|||
* @param string $key Key to check. |
|||
* @return void |
|||
* @throws \Cake\Cache\InvalidArgumentException When the key is not valid. |
|||
*/ |
|||
protected function ensureValidKey($key) |
|||
{ |
|||
if (!is_string($key) || strlen($key) === 0) { |
|||
throw new InvalidArgumentException('A cache key must be a non-empty string.'); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Ensure the validity of the given cache keys. |
|||
* |
|||
* @param mixed $keys The keys to check. |
|||
* @return void |
|||
* @throws \Cake\Cache\InvalidArgumentException When the keys are not valid. |
|||
*/ |
|||
protected function ensureValidKeys($keys) |
|||
{ |
|||
if (!is_array($keys) && !($keys instanceof \Traversable)) { |
|||
throw new InvalidArgumentException('A cache key set must be either an array or a Traversable.'); |
|||
} |
|||
|
|||
foreach ($keys as $key) { |
|||
$this->ensureValidKey($key); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Fetches the value for a given key from the cache. |
|||
* |
|||
* @param string $key The unique key of this item in the cache. |
|||
* @param mixed $default Default value to return if the key does not exist. |
|||
* @return mixed The value of the item from the cache, or $default in case of cache miss. |
|||
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value. |
|||
*/ |
|||
public function get($key, $default = null) |
|||
{ |
|||
$this->ensureValidKey($key); |
|||
$result = $this->innerEngine->read($key); |
|||
if ($result === false) { |
|||
return $default; |
|||
} |
|||
|
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* Persists data in the cache, uniquely referenced by the given key with an optional expiration TTL time. |
|||
* |
|||
* @param string $key The key of the item to store. |
|||
* @param mixed $value The value of the item to store, must be serializable. |
|||
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and |
|||
* the driver supports TTL then the library may set a default value |
|||
* for it or let the driver take care of that. |
|||
* @return bool True on success and false on failure. |
|||
* @throws \Cake\Cache\InvalidArgumentException |
|||
* MUST be thrown if the $key string is not a legal value. |
|||
*/ |
|||
public function set($key, $value, $ttl = null) |
|||
{ |
|||
$this->ensureValidKey($key); |
|||
if ($ttl !== null) { |
|||
$restore = $this->innerEngine->getConfig('duration'); |
|||
$this->innerEngine->setConfig('duration', $ttl); |
|||
} |
|||
try { |
|||
$result = $this->innerEngine->write($key, $value); |
|||
|
|||
return (bool)$result; |
|||
} finally { |
|||
if (isset($restore)) { |
|||
$this->innerEngine->setConfig('duration', $restore); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Delete an item from the cache by its unique key. |
|||
* |
|||
* @param string $key The unique cache key of the item to delete. |
|||
* @return bool True if the item was successfully removed. False if there was an error. |
|||
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value. |
|||
*/ |
|||
public function delete($key) |
|||
{ |
|||
$this->ensureValidKey($key); |
|||
|
|||
return $this->innerEngine->delete($key); |
|||
} |
|||
|
|||
/** |
|||
* Wipes clean the entire cache's keys. |
|||
* |
|||
* @return bool True on success and false on failure. |
|||
*/ |
|||
public function clear() |
|||
{ |
|||
return $this->innerEngine->clear(false); |
|||
} |
|||
|
|||
/** |
|||
* Obtains multiple cache items by their unique keys. |
|||
* |
|||
* @param iterable $keys A list of keys that can obtained in a single operation. |
|||
* @param mixed $default Default value to return for keys that do not exist. |
|||
* @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. |
|||
* @throws \Cake\Cache\InvalidArgumentException If $keys is neither an array nor a Traversable, |
|||
* or if any of the $keys are not a legal value. |
|||
*/ |
|||
public function getMultiple($keys, $default = null) |
|||
{ |
|||
$this->ensureValidKeys($keys); |
|||
|
|||
$results = $this->innerEngine->readMany($keys); |
|||
foreach ($results as $key => $value) { |
|||
if ($value === false) { |
|||
$results[$key] = $default; |
|||
} |
|||
} |
|||
|
|||
return $results; |
|||
} |
|||
|
|||
/** |
|||
* Persists a set of key => value pairs in the cache, with an optional TTL. |
|||
* |
|||
* @param iterable $values A list of key => value pairs for a multiple-set operation. |
|||
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and |
|||
* the driver supports TTL then the library may set a default value |
|||
* for it or let the driver take care of that. |
|||
* @return bool True on success and false on failure. |
|||
* @throws \Cake\Cache\InvalidArgumentException If $values is neither an array nor a Traversable, |
|||
* or if any of the $values are not a legal value. |
|||
*/ |
|||
public function setMultiple($values, $ttl = null) |
|||
{ |
|||
$this->ensureValidKeys(array_keys($values)); |
|||
|
|||
if ($ttl !== null) { |
|||
$restore = $this->innerEngine->getConfig('duration'); |
|||
$this->innerEngine->setConfig('duration', $ttl); |
|||
} |
|||
try { |
|||
$result = $this->innerEngine->writeMany($values); |
|||
foreach ($result as $key => $success) { |
|||
if ($success === false) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} finally { |
|||
if (isset($restore)) { |
|||
$this->innerEngine->setConfig('duration', $restore); |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Deletes multiple cache items in a single operation. |
|||
* |
|||
* @param iterable $keys A list of string-based keys to be deleted. |
|||
* @return bool True if the items were successfully removed. False if there was an error. |
|||
* @throws \Cake\Cache\InvalidArgumentException If $keys is neither an array nor a Traversable, |
|||
* or if any of the $keys are not a legal value. |
|||
*/ |
|||
public function deleteMultiple($keys) |
|||
{ |
|||
$this->ensureValidKeys($keys); |
|||
|
|||
$result = $this->innerEngine->deleteMany($keys); |
|||
foreach ($result as $key => $success) { |
|||
if ($success === false) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* Determines whether an item is present in the cache. |
|||
* |
|||
* NOTE: It is recommended that has() is only to be used for cache warming type purposes |
|||
* and not to be used within your live applications operations for get/set, as this method |
|||
* is subject to a race condition where your has() will return true and immediately after, |
|||
* another script can remove it making the state of your app out of date. |
|||
* |
|||
* @param string $key The cache item key. |
|||
* @return bool |
|||
* @throws \Cake\Cache\InvalidArgumentException If the $key string is not a legal value. |
|||
*/ |
|||
public function has($key) |
|||
{ |
|||
return $this->get($key) !== null; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function add($key, $value) |
|||
{ |
|||
return $this->innerEngine->add($key, $value); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function increment($key, $offset = 1) |
|||
{ |
|||
return $this->innerEngine->increment($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function decrement($key, $offset = 1) |
|||
{ |
|||
return $this->innerEngine->decrement($key, $offset); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritDoc} |
|||
*/ |
|||
public function clearGroup($group) |
|||
{ |
|||
return $this->innerEngine->clearGroup($group); |
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue