diff --git a/.travis.yml b/.travis.yml index 1b13c164c..928850880 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ os: linux dist: bionic language: php +sudo: true services: - memcached @@ -29,6 +30,8 @@ jobs: - php: nightly before_install: + - sudo apt remove cmake + - pip install cmake --upgrade # Memcached is not yet available for PHP8 (hasn't been updated since 2019): https://pecl.php.net/package/memcached # Memcache however seems to be compatible with PHP 7 and 8: https://pecl.php.net/package/memcache # @@ -40,7 +43,7 @@ before_install: yes | pecl install -f mongodb-stable; yes | pecl install -f apcu-stable || true; yes | pecl install -f memcache; - yes | pecl install -f couchbase-stable; + yes | pecl install -f couchbase-3.2.2; - | if [[ $TRAVIS_PHP_VERSION == "8."* || $TRAVIS_PHP_VERSION == "nightly" ]]; then phpenv config-add bin/ci/php8_phpfastcache.ini; diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2d881b9..35c484e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## 8.1.4 +#### _"Masks fell, for good.."_ +##### 12 february 2023 +- __Core__ + - Fixed #893 // getItemsByTag() - empty after one item has expired + +## 8.1.3 +#### _"Don't throw the masks, yet."_ +##### 25 may 2022 +- __Core__ + - Fixed #860 // Cache item throw an error on save with DateTimeImmutable date objects +- __Drivers__ + - Fixed #866 // Deprecated Method Cassandra\ExecutionOptions starting of Cassandra 1.3 + +## 8.1.2 +#### _"Free the masks"_ +##### 04 march 2022 +- __Drivers__ + - Fixed #853 // Configuration validation issue with Memcached socket (path) + +## 8.1.1 +#### _"Re-re-Vaccinated"_ +##### 21 february 2022 +- __Core__ + - Fixed #848 // Others PHP 8.1 compatibility bugs + +## 8.1.0 +#### _"Re-Vaccinated"_ +##### 05 january 2022 +- __Core__ + - Fixed #831 // Bug in the PSR-16 getMultiple method +- __Utils__ + - Fixed #846 // PHP 8.1 compatibility bug +- __Drivers__ + - Fixed #840 // Invalid type hint found for "htaccess", expected "string" got "boolean" for leveldb driver +- __Misc__ + - Updated some docs files (fixed typos) +- __Tests__ + - Migrate all Travis tests on bionic + ## 8.0.8 #### _"Sanitary-passed"_ ##### 18 august 2021 diff --git a/bin/ci/scripts/setup_couchbase.sh b/bin/ci/scripts/setup_couchbase.sh index 142b1067f..8496a4f4c 100755 --- a/bin/ci/scripts/setup_couchbase.sh +++ b/bin/ci/scripts/setup_couchbase.sh @@ -2,9 +2,9 @@ set -e -export CB_VERSION=7.0.0 +export CB_VERSION=7.0.2 export CB_RELEASE_URL=https://packages.couchbase.com/releases -export CB_PACKAGE=couchbase-server-community_7.0.0-ubuntu18.04_amd64.deb +export CB_PACKAGE=couchbase-server-community_7.0.2-ubuntu18.04_amd64.deb # Community Edition requires that all nodes provision all services or data service only export SERVICES="kv,n1ql,index,fts" @@ -40,7 +40,7 @@ sudo apt-get update sudo apt-get install -yq libcouchbase3 libcouchbase-dev build-essential libssl1.0.0 runit wget python-httplib2 chrpath tzdata lsof lshw sysstat net-tools numactl echo "# Downloading couchbase" -wget -q -N $CB_RELEASE_URL/$CB_VERSION/$CB_PACKAGE +wget -q -N $CB_RELEASE_URL/$CB_VERSION/$CB_PACKAGE || echo "Failed to download Couchbase $CB_RELEASE_URL" sudo dpkg -i ./$CB_PACKAGE && rm -f ./$CB_PACKAGE # Wait until it's ready diff --git a/lib/Phpfastcache/Cluster/ClusterPoolAbstract.php b/lib/Phpfastcache/Cluster/ClusterPoolAbstract.php index de03caabc..282a5c08f 100644 --- a/lib/Phpfastcache/Cluster/ClusterPoolAbstract.php +++ b/lib/Phpfastcache/Cluster/ClusterPoolAbstract.php @@ -162,9 +162,8 @@ protected function getStandardizedItem(ExtendedCacheItemInterface $item, Extende if ($driverPool === $this) { /** @var ExtendedCacheItemInterface $itemPool */ $itemClass = $driverPool->getClassNamespace() . '\\' . 'Item'; - $itemPool = new $itemClass($this, $item->getKey()); - $itemPool->setEventManager($this->getEventManager()) - ->set($item->get()) + $itemPool = new $itemClass($this, $item->getKey(), $this->getEventManager()); + $itemPool->set($item->get()) ->setHit($item->isHit()) ->setTags($item->getTags()) ->expiresAt($item->getExpirationDate()) diff --git a/lib/Phpfastcache/Cluster/Drivers/FullReplication/FullReplicationCluster.php b/lib/Phpfastcache/Cluster/Drivers/FullReplication/FullReplicationCluster.php index fe4e424c9..539df848b 100644 --- a/lib/Phpfastcache/Cluster/Drivers/FullReplication/FullReplicationCluster.php +++ b/lib/Phpfastcache/Cluster/Drivers/FullReplication/FullReplicationCluster.php @@ -75,7 +75,12 @@ public function getItem($key) } } - return $this->getStandardizedItem($item ?? new Item($this, $key), $this); + if ($item === null) { + $item = new Item($this, $key, $this->getEventManager()); + $item->expiresAfter(abs($this->getConfig()->getDefaultTtl())); + } + + return $this->getStandardizedItem($item, $this); } /** diff --git a/lib/Phpfastcache/Cluster/Drivers/MasterSlaveReplication/MasterSlaveReplicationCluster.php b/lib/Phpfastcache/Cluster/Drivers/MasterSlaveReplication/MasterSlaveReplicationCluster.php index 014367926..cb129ec17 100644 --- a/lib/Phpfastcache/Cluster/Drivers/MasterSlaveReplication/MasterSlaveReplicationCluster.php +++ b/lib/Phpfastcache/Cluster/Drivers/MasterSlaveReplication/MasterSlaveReplicationCluster.php @@ -62,7 +62,7 @@ public function getItem($key) static function (ExtendedCacheItemPoolInterface $pool) use ($key) { return $pool->getItem($key); } - ) ?? new Item($this, $key), + ) ?? (new Item($this, $key, $this->getEventManager()))->expiresAfter(abs($this->getConfig()->getDefaultTtl())), $this ); } diff --git a/lib/Phpfastcache/Cluster/Drivers/SemiReplication/SemiReplicationCluster.php b/lib/Phpfastcache/Cluster/Drivers/SemiReplication/SemiReplicationCluster.php index f3098f714..78c0e7223 100644 --- a/lib/Phpfastcache/Cluster/Drivers/SemiReplication/SemiReplicationCluster.php +++ b/lib/Phpfastcache/Cluster/Drivers/SemiReplication/SemiReplicationCluster.php @@ -54,7 +54,12 @@ public function getItem($key) throw new PhpfastcacheReplicationException('Every pools thrown an exception'); } - return $this->getStandardizedItem($item ?? new Item($this, $key), $this); + if ($item === null) { + $item = new Item($this, $key, $this->getEventManager()); + $item->expiresAfter(abs($this->getConfig()->getDefaultTtl())); + } + + return $this->getStandardizedItem($item, $this); } /** diff --git a/lib/Phpfastcache/Cluster/ItemAbstract.php b/lib/Phpfastcache/Cluster/ItemAbstract.php index 088465928..5ac94bca3 100644 --- a/lib/Phpfastcache/Cluster/ItemAbstract.php +++ b/lib/Phpfastcache/Cluster/ItemAbstract.php @@ -18,6 +18,7 @@ use Phpfastcache\Core\Item\{ExtendedCacheItemInterface, ItemBaseTrait}; use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface; +use Phpfastcache\Event\EventManagerInterface; use Phpfastcache\Exceptions\{PhpfastcacheInvalidArgumentException}; /** @@ -30,6 +31,12 @@ abstract class ItemAbstract implements ExtendedCacheItemInterface ItemBaseTrait::__construct as __BaseConstruct; } + public function __construct(ExtendedCacheItemPoolInterface $driver, $key, EventManagerInterface $em) + { + $this->setEventManager($em); + $this->__BaseConstruct($driver, $key); + } + /** * @param ExtendedCacheItemPoolInterface $driver * @return static diff --git a/lib/Phpfastcache/Core/Item/ItemBaseTrait.php b/lib/Phpfastcache/Core/Item/ItemBaseTrait.php index 1633af033..9a4c6e601 100644 --- a/lib/Phpfastcache/Core/Item/ItemBaseTrait.php +++ b/lib/Phpfastcache/Core/Item/ItemBaseTrait.php @@ -162,7 +162,7 @@ public function expiresAt($expiration): ExtendedCacheItemInterface * @param DateTimeInterface $expiration */ $this->eventManager->dispatch('CacheItemExpireAt', $this, $expiration); - $this->expirationDate = $expiration; + $this->expirationDate = $this->demutateDatetime($expiration); } else { throw new PhpfastcacheInvalidArgumentException('$expiration must be an object implementing the DateTimeInterface got: ' . \gettype($expiration)); } @@ -211,4 +211,11 @@ public function expiresAfter($time) return $this; } + + protected function demutateDatetime(\DateTimeInterface $dateTime): \DateTimeInterface + { + return $dateTime instanceof \DateTimeImmutable + ? \DateTime::createFromImmutable($dateTime) + : $dateTime; + } } diff --git a/lib/Phpfastcache/Core/Item/ItemExtendedTrait.php b/lib/Phpfastcache/Core/Item/ItemExtendedTrait.php index 65456d409..be379dfd7 100644 --- a/lib/Phpfastcache/Core/Item/ItemExtendedTrait.php +++ b/lib/Phpfastcache/Core/Item/ItemExtendedTrait.php @@ -336,6 +336,7 @@ public function getDataAsJsonString(int $option = 0, int $depth = 512): string * Implements \JsonSerializable interface * @return mixed */ + #[\ReturnTypeWillChange] // PHP 8.1 compatibility public function jsonSerialize() { return $this->get(); diff --git a/lib/Phpfastcache/Core/Item/TaggableCacheItemTrait.php b/lib/Phpfastcache/Core/Item/TaggableCacheItemTrait.php index 450957add..d07155098 100644 --- a/lib/Phpfastcache/Core/Item/TaggableCacheItemTrait.php +++ b/lib/Phpfastcache/Core/Item/TaggableCacheItemTrait.php @@ -63,12 +63,10 @@ public function addTag(string $tagName): ExtendedCacheItemInterface */ public function setTags(array $tags): ExtendedCacheItemInterface { - if (\count($tags)) { - if (\array_filter($tags, 'is_string')) { - $this->tags = $tags; - } else { - throw new PhpfastcacheInvalidArgumentException('$tagName must be an array of string'); - } + if ($tags === [] || \array_filter($tags, 'is_string')) { + $this->tags = $tags; + } else { + throw new PhpfastcacheInvalidArgumentException('$tagName must be an array of string'); } return $this; diff --git a/lib/Phpfastcache/Core/Pool/DriverBaseTrait.php b/lib/Phpfastcache/Core/Pool/DriverBaseTrait.php index 708b8198b..e743dc89c 100644 --- a/lib/Phpfastcache/Core/Pool/DriverBaseTrait.php +++ b/lib/Phpfastcache/Core/Pool/DriverBaseTrait.php @@ -17,6 +17,7 @@ namespace Phpfastcache\Core\Pool; use DateTime; +use DateTimeInterface; use Exception; use Phpfastcache\Config\ConfigurationOption; use Phpfastcache\Core\Item\ExtendedCacheItemInterface; @@ -179,27 +180,27 @@ public function driverUnwrapData(array $wrapper) /** * @param array $wrapper - * @return DateTime + * @return ?DateTimeInterface */ - public function driverUnwrapEdate(array $wrapper) + public function driverUnwrapEdate(array $wrapper): ?DateTimeInterface { return $wrapper[self::DRIVER_EDATE_WRAPPER_INDEX]; } /** * @param array $wrapper - * @return DateTime + * @return ?DateTimeInterface */ - public function driverUnwrapCdate(array $wrapper) + public function driverUnwrapCdate(array $wrapper): ?DateTimeInterface { return $wrapper[self::DRIVER_CDATE_WRAPPER_INDEX]; } /** * @param array $wrapper - * @return DateTime + * @return ?DateTimeInterface */ - public function driverUnwrapMdate(array $wrapper) + public function driverUnwrapMdate(array $wrapper) :?DateTimeInterface { return $wrapper[self::DRIVER_MDATE_WRAPPER_INDEX]; } diff --git a/lib/Phpfastcache/Core/Pool/TaggableCacheItemPoolTrait.php b/lib/Phpfastcache/Core/Pool/TaggableCacheItemPoolTrait.php index 763d7c616..1fdcbc171 100644 --- a/lib/Phpfastcache/Core/Pool/TaggableCacheItemPoolTrait.php +++ b/lib/Phpfastcache/Core/Pool/TaggableCacheItemPoolTrait.php @@ -361,8 +361,17 @@ protected function driverWriteTags(ExtendedCacheItemInterface $item): bool * that has slow performances */ - $tagsItem->set(\array_merge((array)$data, [$item->getKey() => $expTimestamp])) - ->expiresAt($item->getExpirationDate()); + $data = \array_merge((array)$data, [$item->getKey() => $expTimestamp]); + $tagsItem->set($data); + + /** + * Recalculate the expiration date + * + * If the $tagsItem does not have + * any cache item references left + * then remove it from tagsItems index + */ + $tagsItem->expiresAt((new DateTime())->setTimestamp(max($data))); $this->driverWrite($tagsItem); $tagsItem->setHit(true); diff --git a/lib/Phpfastcache/Drivers/Cassandra/Config.php b/lib/Phpfastcache/Drivers/Cassandra/Config.php index 82253ca85..c1165131a 100644 --- a/lib/Phpfastcache/Drivers/Cassandra/Config.php +++ b/lib/Phpfastcache/Drivers/Cassandra/Config.php @@ -49,6 +49,11 @@ class Config extends ConfigurationOption */ protected $sslVerify = false; + /** + * @var bool + */ + protected $useLegacyExecutionOptions = false; + /** * @return string */ @@ -174,4 +179,22 @@ public function setSslVerify(bool $sslVerify): self $this->sslVerify = $sslVerify; return $this; } -} \ No newline at end of file + + /** + * @return bool + */ + public function isUseLegacyExecutionOptions(): bool + { + return $this->useLegacyExecutionOptions; + } + + /** + * @param bool $useLegacyExecutionOptions + * @return $this + */ + public function setUseLegacyExecutionOptions(bool $useLegacyExecutionOptions): self + { + $this->useLegacyExecutionOptions = $useLegacyExecutionOptions; + return $this; + } +} diff --git a/lib/Phpfastcache/Drivers/Cassandra/Driver.php b/lib/Phpfastcache/Drivers/Cassandra/Driver.php index e2830ed23..ee050e788 100644 --- a/lib/Phpfastcache/Drivers/Cassandra/Driver.php +++ b/lib/Phpfastcache/Drivers/Cassandra/Driver.php @@ -167,7 +167,7 @@ protected function driverConnect(): bool protected function driverRead(CacheItemInterface $item) { try { - $options = new Cassandra\ExecutionOptions( + $options = $this->getCompatibleExecutionOptionsArgument( [ 'arguments' => ['cache_id' => $item->getKey()], 'page_size' => 1, @@ -203,7 +203,7 @@ protected function driverWrite(CacheItemInterface $item): bool if ($item instanceof Item) { try { $cacheData = $this->encode($this->driverPreWrap($item)); - $options = new Cassandra\ExecutionOptions( + $options = $this->getCompatibleExecutionOptionsArgument( [ 'arguments' => [ 'cache_uuid' => new Cassandra\Uuid(), @@ -267,7 +267,7 @@ protected function driverDelete(CacheItemInterface $item): bool */ if ($item instanceof Item) { try { - $options = new Cassandra\ExecutionOptions( + $options = $this->getCompatibleExecutionOptionsArgument( [ 'arguments' => [ 'cache_id' => $item->getKey(), @@ -320,4 +320,17 @@ protected function driverClear(): bool return false; } } + + /** + * @param array $options + * @return array|Cassandra\ExecutionOptions + */ + protected function getCompatibleExecutionOptionsArgument(array $options) + { + if ($this->getConfig()->isUseLegacyExecutionOptions()) { + return new Cassandra\ExecutionOptions($options); + } + + return $options; + } } diff --git a/lib/Phpfastcache/Drivers/Leveldb/Config.php b/lib/Phpfastcache/Drivers/Leveldb/Config.php index 58d207413..8d19c47dc 100644 --- a/lib/Phpfastcache/Drivers/Leveldb/Config.php +++ b/lib/Phpfastcache/Drivers/Leveldb/Config.php @@ -21,25 +21,25 @@ class Config extends ConfigurationOption { /** - * @var string + * @var bool */ protected $htaccess = true; /** - * @return string + * @return bool */ - public function getHtaccess(): string + public function getHtaccess(): bool { return $this->htaccess; } /** - * @param string $htaccess + * @param bool $htaccess * @return self */ - public function setHtaccess(string $htaccess): self + public function setHtaccess(bool $htaccess): self { $this->htaccess = $htaccess; return $this; } -} \ No newline at end of file +} diff --git a/lib/Phpfastcache/Drivers/Leveldb/Driver.php b/lib/Phpfastcache/Drivers/Leveldb/Driver.php index 59726c5af..cdcd21635 100644 --- a/lib/Phpfastcache/Drivers/Leveldb/Driver.php +++ b/lib/Phpfastcache/Drivers/Leveldb/Driver.php @@ -63,7 +63,7 @@ public function __destruct() protected function driverRead(CacheItemInterface $item) { $val = $this->instance->get($item->getKey()); - if ($val == false) { + if (!$val) { return null; } diff --git a/lib/Phpfastcache/Drivers/Memcache/Config.php b/lib/Phpfastcache/Drivers/Memcache/Config.php index 6855ecdf9..cf9cedc9a 100644 --- a/lib/Phpfastcache/Drivers/Memcache/Config.php +++ b/lib/Phpfastcache/Drivers/Memcache/Config.php @@ -17,6 +17,7 @@ namespace Phpfastcache\Drivers\Memcache; use Phpfastcache\Config\ConfigurationOption; +use Phpfastcache\Exceptions\PhpfastcacheDriverException; use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException; @@ -28,10 +29,10 @@ class Config extends ConfigurationOption * Multiple server can be added this way: * $cfg->setServers([ * [ + * // If you use an UNIX socket set the host and port to null * 'host' => '127.0.0.1', + * //'path' => 'path/to/unix/socket', * 'port' => 11211, - * 'saslUser' => false, - * 'saslPassword' => false, * ] * ]); */ @@ -47,52 +48,6 @@ class Config extends ConfigurationOption */ protected $port = 11211; - /** - * @var string - */ - protected $saslUser = ''; - - /** - * @var string - */ - protected $saslPassword = ''; - - /** - * @return bool - */ - public function getSaslUser(): string - { - return $this->saslUser; - } - - /** - * @param string $saslUser - * @return self - */ - public function setSaslUser(string $saslUser): self - { - $this->saslUser = $saslUser; - return $this; - } - - /** - * @return string - */ - public function getSaslPassword(): string - { - return $this->saslPassword; - } - - /** - * @param string $saslPassword - * @return self - */ - public function setSaslPassword(string $saslPassword): self - { - $this->saslPassword = $saslPassword; - return $this; - } - /** * @return array */ @@ -109,18 +64,33 @@ public function getServers(): array public function setServers(array $servers): self { foreach ($servers as $server) { - if ($diff = array_diff(['host', 'port', 'saslUser', 'saslPassword'], array_keys($server))) { - throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: ' . implode(', ', $diff)); + if (\array_key_exists('saslUser', $server) || array_key_exists('saslPassword', $server)) { + throw new PhpfastcacheInvalidConfigurationException('Unlike Memcached, Memcache does not support SASL authentication'); } - if ($diff = array_diff(array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])) { + + if ($diff = array_diff(array_keys($server), ['host', 'port', 'path'])) { throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: ' . implode(', ', $diff)); } - if (!is_string($server['host'])) { - throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array'); + + if (!empty($server['host']) && !empty($server['path'])) { + throw new PhpfastcacheInvalidConfigurationException('Host and path cannot be simultaneous defined.'); + } + + if ((isset($server['host']) && !is_string($server['host'])) || (empty($server['path']) && empty($server['host']))) { + throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array if path is not defined'); + } + + if ((isset($server['path']) && !is_string($server['path'])) || (empty($server['host']) && empty($server['path']))) { + throw new PhpfastcacheInvalidConfigurationException('Path must be a valid string in "$server" configuration array if host is not defined'); } - if (!is_int($server['port'])) { + + if (!empty($server['host']) && (empty($server['port']) || !is_int($server['port'])|| $server['port'] < 1)) { throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array'); } + + if (!empty($server['port']) && !empty($server['path'])) { + throw new PhpfastcacheInvalidConfigurationException('Port should not be defined along with path'); + } } $this->servers = $servers; return $this; @@ -141,6 +111,7 @@ public function getHost(): string public function setHost(string $host): self { $this->host = $host; + return $this; } @@ -161,4 +132,4 @@ public function setPort(int $port): self $this->port = $port; return $this; } -} \ No newline at end of file +} diff --git a/lib/Phpfastcache/Drivers/Memcache/Driver.php b/lib/Phpfastcache/Drivers/Memcache/Driver.php index 3274ce600..bc5b5c7a3 100644 --- a/lib/Phpfastcache/Drivers/Memcache/Driver.php +++ b/lib/Phpfastcache/Drivers/Memcache/Driver.php @@ -92,35 +92,28 @@ public function getStats(): DriverStatistic protected function driverConnect(): bool { $this->instance = new MemcacheSoftware(); - $servers = $this->getConfig()->getServers(); - - if (count($servers) < 1) { - $servers = [ - [ - 'host' => $this->getConfig()->getHost(), - 'path' => $this->getConfig()->getPath(), - 'port' => $this->getConfig()->getPort(), - 'saslUser' => $this->getConfig()->getSaslUser() ?: false, - 'saslPassword' => $this->getConfig()->getSaslPassword() ?: false, - ], - ]; + + if (count($this->getConfig()->getServers()) < 1) { + $this->getConfig()->setServers( + [ + [ + 'host' => $this->getConfig()->getHost(), + 'path' => $this->getConfig()->getPath(), + 'port' => $this->getConfig()->getPort(), + ] + ] + ); } - foreach ($servers as $server) { + foreach ($this->getConfig()->getServers() as $server) { try { /** * If path is provided we consider it as an UNIX Socket */ if (!empty($server['path']) && !$this->instance->addServer($server['path'], 0)) { $this->fallback = true; - } else { - if (!empty($server['host']) && !$this->instance->addServer($server['host'], $server['port'])) { - $this->fallback = true; - } - } - - if (!empty($server['saslUser']) && !empty($server['saslPassword'])) { - throw new PhpfastcacheDriverException('Unlike Memcached, Memcache does not support SASL authentication'); + } elseif (!empty($server['host']) && !$this->instance->addServer($server['host'], $server['port'])) { + $this->fallback = true; } } catch (Exception $e) { $this->fallback = true; diff --git a/lib/Phpfastcache/Drivers/Memcached/Config.php b/lib/Phpfastcache/Drivers/Memcached/Config.php index 6469957f3..6726e50ef 100644 --- a/lib/Phpfastcache/Drivers/Memcached/Config.php +++ b/lib/Phpfastcache/Drivers/Memcached/Config.php @@ -28,10 +28,12 @@ class Config extends ConfigurationOption * Multiple server can be added this way: * $cfg->setServers([ * [ + * // If you use an UNIX socket set the host and port to null * 'host' => '127.0.0.1', + * //'path' => 'path/to/unix/socket', * 'port' => 11211, - * 'saslUser' => false, - * 'saslPassword' => false, + * 'saslUser' => null, + * 'saslPassword' => null, * ] * ]); */ @@ -114,18 +116,33 @@ public function getServers(): array public function setServers(array $servers): self { foreach ($servers as $server) { - if ($diff = array_diff(['host', 'port', 'saslUser', 'saslPassword'], array_keys($server))) { - throw new PhpfastcacheInvalidConfigurationException('Missing keys for memcached server: ' . implode(', ', $diff)); - } - if ($diff = array_diff(array_keys($server), ['host', 'port', 'saslUser', 'saslPassword'])) { + if ($diff = array_diff(array_keys($server), ['host', 'port', 'saslUser', 'saslPassword', 'path'])) { throw new PhpfastcacheInvalidConfigurationException('Unknown keys for memcached server: ' . implode(', ', $diff)); } - if (!is_string($server['host'])) { - throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array'); + + if (!empty($server['host']) && !empty($server['path'])) { + throw new PhpfastcacheInvalidConfigurationException('Host and path cannot be simultaneous defined.'); + } + + if ((isset($server['host']) && !is_string($server['host'])) || (empty($server['path']) && empty($server['host']))) { + throw new PhpfastcacheInvalidConfigurationException('Host must be a valid string in "$server" configuration array if path is not defined'); + } + + if ((isset($server['path']) && !is_string($server['path'])) || (empty($server['host']) && empty($server['path']))) { + throw new PhpfastcacheInvalidConfigurationException('Path must be a valid string in "$server" configuration array if host is not defined'); } - if (!is_int($server['port'])) { + + if (!empty($server['host']) && (empty($server['port']) || !is_int($server['port'])|| $server['port'] < 1)) { throw new PhpfastcacheInvalidConfigurationException('Port must be a valid integer in "$server" configuration array'); } + + if (!empty($server['port']) && !empty($server['path'])) { + throw new PhpfastcacheInvalidConfigurationException('Port should not be defined along with path'); + } + + if (!empty($server['saslUser']) && !empty($server['saslPassword']) && (!is_string($server['saslUser']) || !is_string($server['saslPassword']))) { + throw new PhpfastcacheInvalidConfigurationException('If provided, saslUser and saslPassword must be a string'); + } } $this->servers = $servers; return $this; @@ -186,4 +203,4 @@ public function setOptPrefix(string $optPrefix): Config $this->optPrefix = trim($optPrefix); return $this; } -} \ No newline at end of file +} diff --git a/lib/Phpfastcache/Drivers/Memcached/Driver.php b/lib/Phpfastcache/Drivers/Memcached/Driver.php index e890eb41e..df7131b5b 100644 --- a/lib/Phpfastcache/Drivers/Memcached/Driver.php +++ b/lib/Phpfastcache/Drivers/Memcached/Driver.php @@ -96,19 +96,21 @@ protected function driverConnect(): bool $servers = $this->getConfig()->getServers(); - if (count($servers) < 1) { - $servers = [ + if (count($this->getConfig()->getServers()) < 1) { + $this->getConfig()->setServers( [ - 'host' => $this->getConfig()->getHost(), - 'path' => $this->getConfig()->getPath(), - 'port' => $this->getConfig()->getPort(), - 'saslUser' => $this->getConfig()->getSaslUser() ?: false, - 'saslPassword' => $this->getConfig()->getSaslPassword() ?: false, - ], - ]; + [ + 'host' => $this->getConfig()->getHost(), + 'path' => $this->getConfig()->getPath(), + 'port' => $this->getConfig()->getPort(), + 'saslUser' => $this->getConfig()->getSaslUser() ?: null, + 'saslPassword' => $this->getConfig()->getSaslPassword() ?: null, + ], + ] + ); } - foreach ($servers as $server) { + foreach ($this->getConfig()->getServers() as $server) { try { /** * If path is provided we consider it as an UNIX Socket diff --git a/lib/Phpfastcache/Util/ArrayObject.php b/lib/Phpfastcache/Util/ArrayObject.php index c1857ea85..01ee360ad 100644 --- a/lib/Phpfastcache/Util/ArrayObject.php +++ b/lib/Phpfastcache/Util/ArrayObject.php @@ -30,7 +30,7 @@ class ArrayObject implements ArrayAccess, Iterator, Countable /** * @var array */ - private $array = []; + private $array; /** * @var int @@ -49,6 +49,7 @@ public function __construct(...$args) /** * @return mixed */ + #[\ReturnTypeWillChange] // PHP 8.1 compatibility public function current() { return $this->array[$this->position]; @@ -57,7 +58,7 @@ public function current() /** * */ - public function next() + public function next(): void { ++$this->position; } @@ -90,7 +91,7 @@ public function offsetExists($offset): bool /** * */ - public function rewind() + public function rewind(): void { $this->position = 0; } @@ -105,8 +106,9 @@ public function count(): int /** * @param mixed $offset - * @return mixed|null + * @return mixed */ + #[\ReturnTypeWillChange] // PHP 8.1 compatibility public function offsetGet($offset) { return $this->array[$offset] ?? null; @@ -116,7 +118,7 @@ public function offsetGet($offset) * @param mixed $offset * @param mixed $value */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { // NOTE: THIS IS THE FIX FOR THE ISSUE "Indirect modification of overloaded element of SplFixedArray has no effect" // NOTE: WHEN APPENDING AN ARRAY (E.G. myArr[] = 5) THE KEY IS NULL, SO WE TEST FOR THIS CONDITION BELOW, AND VOILA @@ -131,15 +133,15 @@ public function offsetSet($offset, $value) /** * @param mixed $offset */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->array[$offset]); } /** - * @return array|mixed + * @return array */ - public function toArray() + public function toArray(): array { return $this->array; } @@ -148,7 +150,7 @@ public function toArray() * @param array $array * @return self */ - public function mergeArray($array): self + public function mergeArray(array $array): self { $this->array = array_merge($this->array, $array); @@ -156,10 +158,10 @@ public function mergeArray($array): self } /** - * @return array|mixed + * @return array */ - protected function &getArray() + protected function &getArray(): array { return $this->array; } -} \ No newline at end of file +} diff --git a/tests/Memcache.test.php b/tests/Memcache.test.php index d324e0d1e..1d6cf0240 100644 --- a/tests/Memcache.test.php +++ b/tests/Memcache.test.php @@ -6,7 +6,7 @@ */ use Phpfastcache\CacheManager; -use Phpfastcache\Drivers\Memcache\Config as MemcachedConfig; +use Phpfastcache\Drivers\Memcache\Config as MemcacheConfig; use Phpfastcache\Tests\Helper\TestHelper; chdir(__DIR__); @@ -14,7 +14,15 @@ $testHelper = new TestHelper('Memcache driver'); -$config = new MemcachedConfig(); +$config = new MemcacheConfig([ + 'servers' => [ + [ + 'path' => '', + 'host' => '127.0.0.1', + 'port' => 11211, + ] + ] +]); $config->setItemDetailedDate(true); $cacheInstance = CacheManager::getInstance('Memcache', $config); $testHelper->runCRUDTests($cacheInstance); diff --git a/tests/MemcachedAlternativeConfigurationSyntax.test.php b/tests/Memcached.test.php similarity index 96% rename from tests/MemcachedAlternativeConfigurationSyntax.test.php rename to tests/Memcached.test.php index d8dd3863f..e73c815a5 100644 --- a/tests/MemcachedAlternativeConfigurationSyntax.test.php +++ b/tests/Memcached.test.php @@ -11,7 +11,7 @@ chdir(__DIR__); require_once __DIR__ . '/../vendor/autoload.php'; -$testHelper = new TestHelper('Memcached alternative configuration syntax'); +$testHelper = new TestHelper('Memcached'); $cacheInstanceDefSyntax = CacheManager::getInstance('Memcached'); diff --git a/tests/issues/Github-853.test.php b/tests/issues/Github-853.test.php new file mode 100644 index 000000000..bb9bdb20e --- /dev/null +++ b/tests/issues/Github-853.test.php @@ -0,0 +1,371 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + */ + +use Phpfastcache\CacheManager; +use Phpfastcache\Drivers\Memcached\Config as MemcachedConfig; +use Phpfastcache\Drivers\Memcache\Config as MemcacheConfig; +use Phpfastcache\Exceptions\PhpfastcacheDriverCheckException; +use Phpfastcache\Exceptions\PhpfastcacheInvalidConfigurationException; +use Phpfastcache\Tests\Helper\TestHelper; + +chdir(__DIR__); +require_once __DIR__ . '/../../vendor/autoload.php'; +$testHelper = new TestHelper('Github issue #675 - Configuration validation issue with Memcached socket (path)'); + +/** + * MEMCACHED ASSERTIONS + */ +$testHelper->printInfoText('Testing MEMCACHED configuration validation errors...'); +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => '', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] An empty host and path did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] An empty host and path thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => 'a/nice/path', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Both path and host configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Both path and host configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => 'a/nice/path', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Both path and port configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Both path and port configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => true, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Invalid non-string path value not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Invalid non-string path value thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => true, + 'path' => '', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Invalid non-string host value not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Invalid non-string host value thrown an exception: ' . $e->getMessage()); +} + + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] An host without port configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] An host without port configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'unknown_key_1' => '', + 'unknown_key_2' => true, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Unknowns keys configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Unknowns keys configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => '', + 'port' => 11211, + 'saslUser' => true, + 'saslPassword' => [1337], + ] + ] + ]); + $testHelper->assertFail('[MEMCACHED] Both saslUser and saslPassword misconfigurations did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHED] Both saslUser and saslPassword misconfigurations thrown an exception: ' . $e->getMessage()); +} + + +/** + * MEMCACHE ASSERTIONS + */ +$testHelper->printNewLine(); +$testHelper->printInfoText('Testing MEMCACHE configuration validation errors...'); +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => '', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] An empty host and path did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] An empty host and path thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => 'a/nice/path', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Both path and host configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Both path and host configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => 'a/nice/path', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Both path and port configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Both path and port configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => true, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Invalid non-string path value not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Invalid non-string path value thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => true, + 'path' => '', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Invalid non-string host value not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Invalid non-string host value thrown an exception: ' . $e->getMessage()); +} + + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] An host without port configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] An host without port configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'unknown_key_1' => '', + 'unknown_key_2' => true, + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Unknowns keys configured did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Unknowns keys configured thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => 'a/nice/path', + 'port' => 11211, + 'saslUser' => 'lorem', + 'saslPassword' => 'ipsum', + ] + ] + ]); + $testHelper->assertFail('[MEMCACHE] Unsupported SASL configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertPass('[MEMCACHE] Unsupported SASL configuration thrown an exception: ' . $e->getMessage()); +} + +/** + * GOOD CONFIGURATIONS ASSERTIONS + */ +$testHelper->printNewLine(); +$testHelper->printInfoText('Testing valid configurations...'); +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => 'a/nice/path', + 'port' => null, + ] + ] + ]); + $testHelper->assertPass('[MEMCACHE] Valid Memcache socket configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHE] Valid Memcache socket configuration thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcacheConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => '', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertPass('[MEMCACHE] Valid Memcache host configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHE] Valid Memcache host configuration thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '', + 'path' => 'a/nice/path', + 'port' => null, + ] + ] + ]); + $testHelper->assertPass('[MEMCACHED] Valid Memcached socket configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHED] Valid Memcached socket configuration thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => '', + 'port' => 11211, + ] + ] + ]); + $testHelper->assertPass('[MEMCACHED] Valid Memcached host configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHED] Valid Memcached host configuration thrown an exception: ' . $e->getMessage()); +} + +try { + new MemcachedConfig([ + 'servers' => [ + [ + 'host' => '127.0.0.1', + 'path' => '', + 'port' => 11211, + 'saslUser' => 'lorem', + 'saslPassword' => 'ipsum', + ] + ] + ]); + $testHelper->assertPass('[MEMCACHED] Valid Memcached host + SASL authentication configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHED] Valid Memcached host + SASL authentication configuration thrown an exception: ' . $e->getMessage()); +} + +/** + * BASIC CONFIGURATIONS ASSERTIONS + */ +$testHelper->printNewLine(); +$testHelper->printInfoText('Testing basic configurations...'); + +try { + CacheManager::getInstance('Memcached', new MemcachedConfig()); + $testHelper->assertPass('[MEMCACHED] Default configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHED] Default configuration thrown an exception: ' . $e->getMessage()); +} catch (PhpfastcacheDriverCheckException $e){ + // If the driver fails to initialize it is not an issue, the validation process has at least succeeded + $testHelper->assertPass('[MEMCACHED] Default configuration did not thrown an exception (with failed driver initialization)'); +} + +try { + CacheManager::getInstance('Memcache', new MemcacheConfig()); + $testHelper->assertPass('[MEMCACHE] Default configuration did not thrown an exception'); +} catch (PhpfastcacheInvalidConfigurationException $e){ + $testHelper->assertFail('[MEMCACHE] Default configuration thrown an exception: ' . $e->getMessage()); +} catch (PhpfastcacheDriverCheckException $e){ + // If the driver fails to initialize it is not an issue, the validation process has at least succeeded + $testHelper->assertPass('[MEMCACHE] Default configuration did not thrown an exception (with failed driver initialization)'); +} + +$testHelper->terminateTest(); diff --git a/tests/issues/Github-860.test.php b/tests/issues/Github-860.test.php new file mode 100644 index 000000000..183acbc96 --- /dev/null +++ b/tests/issues/Github-860.test.php @@ -0,0 +1,34 @@ + https://www.phpfastcache.com + * @author Georges.L (Geolim4) + */ + +use Phpfastcache\CacheManager; +use Phpfastcache\Drivers\Files\Config as FilesConfig; +use Phpfastcache\Tests\Helper\TestHelper; + +chdir(__DIR__); +require_once __DIR__ . '/../../vendor/autoload.php'; +$testHelper = new TestHelper('Github issue #860 - Cache item throw an error on save with DateTimeImmutable date objects'); + +$config = new FilesConfig(); +$config->setItemDetailedDate(true); +$cacheInstance = CacheManager::getInstance('Files', $config); +$cacheInstance->clear(); + +try { + $key = 'pfc_' . bin2hex(random_bytes(12)); + $item = $cacheInstance->getItem($key); + $item->set(random_int(1000, 999999)) + ->setExpirationDate(new DateTimeImmutable('+1 month')) + ->setCreationDate(new DateTimeImmutable()) + ->setModificationDate(new DateTimeImmutable('+1 week')); + $cacheInstance->save($item); + $cacheInstance->detachAllItems(); + $item = $cacheInstance->getItem($key); + $testHelper->assertPass('Github issue #860 have not regressed.'); +} catch (\TypeError $e) { + $testHelper->assertFail('Github issue #860 have regressed, exception caught: ' . $e->getMessage()); +} diff --git a/tests/lib/Helper/TestHelper.php b/tests/lib/Helper/TestHelper.php index f590972aa..bb624df63 100644 --- a/tests/lib/Helper/TestHelper.php +++ b/tests/lib/Helper/TestHelper.php @@ -202,7 +202,7 @@ public function printNoteText(string $string): self */ public function printNewLine(int $count = 1): self { - $this->climate->out(str_repeat(PHP_EOL, $count)); + $this->climate->out(str_repeat(PHP_EOL, $count - 1)); return $this; } @@ -349,8 +349,12 @@ public function accessInaccessibleMember($obj, $prop) */ public function errorHandler(int $errno, string $errstr, string $errfile, int $errline) { - $errorType = ''; + // Silenced errors + if (!(error_reporting() & $errno)){ + return; + } + $errorType = ''; switch ($errno) { case E_PARSE: case E_ERROR: @@ -433,6 +437,18 @@ public function runCRUDTests(ExtendedCacheItemPoolInterface $pool, bool $poolCle $cacheItem = $pool->getItem($cacheKey); $this->printInfoText('Using cache key: ' . $cacheKey); + /** + * Default TTL - 1sec is for dealing with potential script execution delay + * @see https://github.com/PHPSocialNetwork/phpfastcache/issues/855 + */ + if($cacheItem->getTtl() < $pool->getConfig()->getDefaultTtl() - 1) { + $this->assertFail(\sprintf( + 'The expected TTL of the cache item was ~%ds, got %ds', + $pool->getConfig()->getDefaultTtl(), + $cacheItem->getTtl() + )); + } + $cacheItem->set($cacheValue) ->expiresAfter(60) ->addTags([$cacheTag, $cacheTag2]);