Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]

### Changed
- Added scalar, nullable, and `mixed` type declarations to the public cache API
and its configuration properties. Calls that already use the documented value
types remain compatible.
- PHPStan analysis now runs at level 8.
- **BREAKING**: The `SimplePhpCache` global class is now
`Tschueller\SimplePhpCache\SimplePhpCache`, loaded from `src/` through PSR-4.
Manual includes must be replaced with Composer's autoloader. See the migration
Expand Down
31 changes: 11 additions & 20 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@

Deferred improvements — implement when explicitly requested or when capacity allows.

## Medium Priority
## v1.x

- **[MEDIUM] Add proper return types and parameter types to all public methods**
Current code has no type declarations. Requires PHP 8.x syntax review.
Non-breaking as long as callers pass correct types.
Prerequisite for raising PHPStan level beyond 3.

- **[MEDIUM] Raise PHPStan analysis level (target: 6–8)**
Currently level 3. Level 10 reveals 16 findings, all related to missing type declarations.
Can only be raised sustainably after type declarations are added.

- **[MEDIUM] Implement PSR-16 SimpleCache interface**
Makes the library interoperable with PSR-16 consumers. Requires namespace + new
API surface. Large refactor — deserves its own PR.
- **[MEDIUM] Make cache writes atomic and protect readers from partial files**
`LOCK_EX` does not prevent readers from observing a file while it is being
written. Add an atomic-write strategy and concurrent-read/write tests.

## Low Priority
- **[MEDIUM] Publish to Packagist**
Run `composer validate --strict`, submit to packagist.org.
Prerequisite: stable tagged 1.0.0 release with namespace.

- **[LOW] Add code coverage reporting to CI (Xdebug/PCOV)**
Track test coverage over time. Optional quality gate.
Expand All @@ -32,10 +25,8 @@ Deferred improvements — implement when explicitly requested or when capacity a
Edge cases: empty string IDs, very long IDs, unreadable cache directory,
Unicode in cache IDs.

- **[MEDIUM] Make cache writes atomic and protect readers from partial files**
`LOCK_EX` does not prevent readers from observing a file while it is being
written. Add an atomic-write strategy and concurrent-read/write tests.
## v2.x

- **[LOW] Publish to Packagist**
Run `composer validate --strict`, submit to packagist.org.
Prerequisite: stable tagged release with namespace.
- **[MEDIUM] Implement PSR-16 SimpleCache interface**
Adds a separate PSR-16 cache API and storage model. Deliberately out of scope
for the stable 1.x SimplePhpCache API; deserves its own major-version PR.
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
parameters:
level: 3
level: 8
paths:
- src
- tests
ignoreErrors: []
48 changes: 29 additions & 19 deletions src/SimplePhpCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ class SimplePhpCache
private const VAR_CACHE_PREFIX = "SPCJSON1:";

/** Cache id from the current started cache. */
private static $startedCache = null;
private static ?string $startedCache = null;

/** The cached content. */
private static $cacheContent = null;
private static mixed $cacheContent = null;

/** Whether the current cache session contains a cached value. */
private static bool $hasCacheContent = false;

/** The cache base directory. */
public static $cacheBaseDir = null;
public static ?string $cacheBaseDir = null;

/** The max cache time. */
public static $maxCacheTime = 86400;
public static int $maxCacheTime = 86400;

/**
* Start the HTML output caching.
Expand All @@ -40,7 +40,7 @@ class SimplePhpCache
* @throws RuntimeException
* When the cache is already started
*/
public static function initHTMLCaching($id, $refresh = false)
public static function initHTMLCaching(string $id, bool $refresh = false): bool
{
if (self::$startedCache != null)
{
Expand Down Expand Up @@ -77,7 +77,7 @@ public static function initHTMLCaching($id, $refresh = false)
* @throws RuntimeException
* When the cache is not started
*/
public static function finishHTMLCaching($id)
public static function finishHTMLCaching(string $id): string
{
if (self::$startedCache != $id)
{
Expand All @@ -86,12 +86,20 @@ public static function finishHTMLCaching($id)

if (self::$hasCacheContent)
{
if (!is_string(self::$cacheContent)) {
throw new RuntimeException("Invalid HTML cache content");
}

$content = self::$cacheContent;
}
else
{
$cacheFile = self::getCacheDir() . "/" . self::getFilename($id);
$content = ob_get_clean();
if ($content === false) {
throw new RuntimeException("Error reading output buffer");
}

if (file_put_contents($cacheFile, $content, LOCK_EX) === false)
throw new RuntimeException("Error writing cache: '$cacheFile'");
}
Expand All @@ -116,7 +124,7 @@ public static function finishHTMLCaching($id)
* @throws RuntimeException
* When the cache is already started
*/
public static function initVarCaching($id, $refresh = false)
public static function initVarCaching(string $id, bool $refresh = false): bool
{
if (self::$startedCache != null)
{
Expand Down Expand Up @@ -163,7 +171,7 @@ public static function initVarCaching($id, $refresh = false)
* The data to cache.
* @throws RuntimeException
*/
public static function setVarCaching($id, $data)
public static function setVarCaching(string $id, mixed $data): void
{
if (self::$startedCache != $id)
{
Expand All @@ -190,7 +198,7 @@ public static function setVarCaching($id, $data)
* @throws RuntimeException
* When the cache is not started
*/
public static function finishVarCaching($id)
public static function finishVarCaching(string $id): mixed
{
if (self::$startedCache != $id)
{
Expand All @@ -214,7 +222,8 @@ public static function finishVarCaching($id)
* @param string $idPrefix
The cache identifier prefix.
*/
public static function clearCache($id = null, $idPrefix = null) {
public static function clearCache(?string $id = null, ?string $idPrefix = null): void
{
if ($id) {
$pattern = self::getFilename($id);
} else if ($idPrefix) {
Expand All @@ -237,7 +246,8 @@ public static function clearCache($id = null, $idPrefix = null) {
* @return int
* The cache file count.
*/
public static function getCacheCount($idPrefix = "") {
public static function getCacheCount(string $idPrefix = ""): int
{
$pattern = self::sanitizeIdPrefix($idPrefix) . "*.cache";
return count(glob(self::getCacheDir() . "/" . $pattern) ?: []);
}
Expand All @@ -247,9 +257,9 @@ public static function getCacheCount($idPrefix = "") {
* @param string $idPrefix
* @return string
*/
private static function sanitizeIdPrefix($idPrefix)
private static function sanitizeIdPrefix(string $idPrefix): string
{
return preg_replace('/[^a-zA-Z0-9_\-.]/', '', (string) $idPrefix);
return preg_replace('/[^a-zA-Z0-9_\-.]/', '', $idPrefix) ?? '';
}

/**
Expand All @@ -260,7 +270,7 @@ private static function sanitizeIdPrefix($idPrefix)
* @return string
* The cache file name.
*/
private static function getFilename($id)
private static function getFilename(string $id): string
{
return urlencode(self::fixPath($id)) . "-" . md5($id) . ".cache";
}
Expand All @@ -271,7 +281,7 @@ private static function getFilename($id)
* @param string $raw
* @return array{0: bool, 1: mixed} Whether the payload is valid and its value.
*/
private static function decodeVarCachePayload($raw)
private static function decodeVarCachePayload(string $raw): array
{
if (!str_starts_with($raw, self::VAR_CACHE_PREFIX)) {
return [false, null];
Expand All @@ -293,7 +303,7 @@ private static function decodeVarCachePayload($raw)
* @param mixed $data
* @throws RuntimeException
*/
private static function writeVarCachePayload($cacheFile, $data)
private static function writeVarCachePayload(string $cacheFile, mixed $data): void
{
try {
$payload = self::VAR_CACHE_PREFIX . json_encode($data, JSON_THROW_ON_ERROR);
Expand All @@ -312,7 +322,7 @@ private static function writeVarCachePayload($cacheFile, $data)
* @param mixed $value
* @return boolean
*/
private static function containsObject($value)
private static function containsObject(mixed $value): bool
{
if (is_object($value)) {
return true;
Expand All @@ -337,7 +347,7 @@ private static function containsObject($value)
* The dir path to fix.
* @return string The fixed path.
*/
private static function fixPath($path)
private static function fixPath(string $path): string
{
return str_replace("\\", "/", $path);
}
Expand All @@ -350,7 +360,7 @@ private static function fixPath($path)
* @throws RuntimeException
* When the cache directory creation failed.
*/
private static function getCacheDir()
private static function getCacheDir(): string
{
if (self::$cacheBaseDir == null) {
self::$cacheBaseDir = sys_get_temp_dir();
Expand Down
Loading