Skip to content

Conversation

manjencic
Copy link
Contributor

@manjencic manjencic commented Oct 14, 2025

Pull Request Checklist (Feature Branch to next):

  • Ich habe die neuesten Änderungen aus dem next Branch in meinen Feature-Branch gemergt.
  • Das Code-Review wurde abgeschlossen.
  • Fachliche Tests wurden durchgeführt und sind abgeschlossen.

Summary by CodeRabbit

  • Chores

    • Added runtime caching libraries.
  • Refactor

    • Introduced application-wide caching with automatic invalidation to speed up read-heavy operations and improve overall performance.
    • Enabled persistent database connections to reduce connection overhead.
  • Tests

    • Updated tests to initialize and manage cache state so test runs remain reliable.

Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds PSR-16 caching (Symfony FilesystemAdapter + Psr16Cache) to zmsapi and zmsdb. Application classes initialize cache on load. Many entity/read methods gain an optional $disableCache parameter; reads use the cache, writes invalidate relevant keys and often re-read with cache bypass.

Changes

Cohort / File(s) Summary
Cache Dependencies
zmsapi/composer.json, zmsdb/composer.json
Added symfony/cache and psr/simple-cache runtime dependencies
Cache Infrastructure
zmsapi/src/Zmsapi/Application.php, zmsdb/src/Zmsdb/Application.php
Added public static ?CacheInterface $cache, $CACHE_DIR, $SOURCE_CACHE_TTL; added initialize(), initializeCache(), validateCacheDirectory(), setupCache(); invoked initialize() at load
Global cache wiring / tests
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
Test setup added to clear \App::$cache and new imports
Entity-wide read-through caching
zmsdb/src/Zmsdb/* (Closure, Cluster, Config, DayOff, Department, Provider, Request, Scope, Source, Link, Availability, Organisation, Link, Provider, Request, Source, Scope, Department, DayOff, Closure, Cluster, Config, Availability)
Many read methods extended with an optional $disableCache (or similar) param; reads construct cache keys, return cached results when present, and set cache on miss. Nested reads propagate the disable flag.
Cache invalidation on mutations
zmsdb/src/Zmsdb/* (Cluster, Config, DayOff, Department, Provider, Request, Scope, Source, Link, Closure, etc.)
Mutation paths (write/update/delete/replace) added calls to delete related cache keys; many classes expose removeCache(...) helpers and post-write reads bypass cache to return fresh entity
Call-site updates in zmsapi
zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php, zmsapi/src/Zmsapi/AvailabilityClosureToggle.php
readEntity() calls updated to pass cache-bypass true where appropriate; Closure::deleteEntity() call changed to pass the closure object
PDO persistent connection option
zmsdb/src/Zmsdb/Connection/Select.php
Added PDO::ATTR_PERSISTENT => true to PDO creation options
Test call updates
zmsdb/tests/Zmsdb/ScopeTest.php, zmsdb/tests/Zmsdb/DepartmentTest.php, zmsdb/tests/Zmsdb/ConfigTest.php, zmsdb/tests/Zmsdb/ProcessTest.php
Updated test call sites to pass the new cache-bypass/flag arguments (e.g., readEntity(..., true))

Sequence Diagram(s)

sequenceDiagram
    participant Loader as Module Load
    participant App as Application
    participant FS as Filesystem
    participant CacheFactory as Symfony FilesystemAdapter -> Psr16Cache

    Loader->>App: Application::initialize()
    activate App
    App->>FS: ensure(CACHE_DIR) / is_writable
    FS-->>App: ok / error
    alt directory writable
        App->>CacheFactory: create FilesystemAdapter($CACHE_DIR,$ttl)
        CacheFactory-->>App: Psr16Cache instance
        App->>App: App::$cache = Psr16Cache
    end
    deactivate App
Loading
sequenceDiagram
    participant Caller
    participant Entity as Repository/Entity
    participant Cache as App::$cache
    participant DB as Database

    Caller->>Entity: readEntity(id, resolve, $disableCache=false)
    activate Entity
        alt cache available and not disabled
            Entity->>Cache: get(cacheKey)
            Cache-->>Entity: cachedValue
            Entity-->>Caller: cachedValue
        else
            Entity->>DB: query...
            DB-->>Entity: result
            opt cache present
                Entity->>Cache: set(cacheKey, result)
                Cache-->>Entity: ok
            end
            Entity-->>Caller: result
        end
    deactivate Entity

    Caller->>Entity: writeEntity(data)
    activate Entity
        Entity->>DB: insert/update/delete
        DB-->>Entity: success
        Entity->>Entity: removeCache(entity)
        Entity->>Cache: delete(key1), delete(key2), ...
        Entity->>Entity: readEntity(id, resolve, $disableCache=true)
        Entity-->>Caller: freshResult
    deactivate Entity
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I dug a cosy cache today,

keys tucked neat where queries play.
When data mutates, I sweep and clear —
fresh hops return, the path is clear.
A tiny rabbit, caching cheer.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title "Feature zmskvr implement cache in zmsdb" contains issues that prevent it from clearly summarizing the changeset. The phrase "Feature zmskvr" appears to be a branch or ticket reference that serves as noise rather than a descriptive term, violating the principle of avoiding unnecessary identifiers. More significantly, the title only mentions zmsdb, but the actual changeset implements caching across both zmsdb and zmsapi (including composer.json updates, Application class modifications, and changes to multiple entity classes in both packages). While the core intent to implement caching is present, the incomplete scope and noisy phrasing make the title insufficiently clear for developers scanning history. Consider revising the title to remove the branch reference and clarify the full scope of changes. A clearer alternative would be something like "Implement caching layer across zmsdb and zmsapi" or "Add PSR-16 cache integration to zmsapi and zmsdb". This would provide readers with an accurate understanding of the changes without extraneous identifiers.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-zmskvr-implement-cache-in-zmsdb

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)

28-29: Remove duplicate checkRights() call.

Line 29 is a duplicate of line 28 that serves no purpose and should be removed.

Apply this diff to remove the duplicate:

 $workstation = (new Helper\User($request, 1))->checkRights('useraccount');
-(new Helper\User($request, 1))->checkRights('useraccount');
 $resolveReferences = Validator::param('resolveReferences')->isNumber()->setDefault(1)->getValue();
zmsdb/src/Zmsdb/Scope.php (2)

21-44: Clarify or remove dual caching layers to prevent stale data.

The method maintains two cache layers:

  1. Static cache (self::$cache): In-memory array cache (line 32, 41, 44)
  2. PSR-16 cache (App::$cache): Filesystem cache (line 26, 42)

Issues:

  • Incomplete cache bypass: Setting $disableCache = true bypasses PSR-16 cache but still checks and populates the static cache (line 32), which is inconsistent.
  • Stale data risk: The static cache is never synchronized when the PSR-16 cache is updated elsewhere (e.g., by another process or explicit cache manipulation).
  • Memory leaks: The static cache grows unbounded and is only cleared on write/update/delete operations (lines 411, 432, 487, 579), not on cache eviction.

Consider one of these approaches:

Option 1: Remove static cache (simplest):

-    public static $cache = [ ];
-
     public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
     {
         $cacheKey = "scope-$scopeId-$resolveReferences";

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey, false);
             if ($data !== false) {
                 return $data;
             }
         }

-        if ($disableCache || ! array_key_exists($cacheKey, self::$cache)) {
             $query = new Query\Scope(Query\Base::SELECT);
             // ... query logic ...
-            self::$cache[$cacheKey] = $this->readResolvedReferences($scope, $resolveReferences);
+            $result = $this->readResolvedReferences($scope, $resolveReferences);
-            App::$cache->set($cacheKey, self::$cache[$cacheKey]);
-        }
-        return self::$cache[$cacheKey];
+            if (!$disableCache) {
+                App::$cache->set($cacheKey, $result);
+            }
+            return $result;
     }

Option 2: Honor $disableCache for both layers:

-        if ($disableCache || ! array_key_exists($cacheKey, self::$cache)) {
+        if ($disableCache || ! array_key_exists($cacheKey, self::$cache)) {
             // ... query logic ...
             self::$cache[$cacheKey] = $this->readResolvedReferences($scope, $resolveReferences);
-            App::$cache->set($cacheKey, self::$cache[$cacheKey]);
+            if (!$disableCache) {
+                App::$cache->set($cacheKey, self::$cache[$cacheKey]);
+            }
         }
         return self::$cache[$cacheKey];

409-439: Invalidate PSR-16 cache on write/update operations.

When writing or updating scopes, the code clears the static cache (self::$cache = [];) but does not invalidate the PSR-16 cache (App::$cache). This causes stale data to be served from the filesystem cache.

Add cache invalidation for the PSR-16 layer. Since you cannot easily determine all affected keys (due to varying $resolveReferences values), consider either:

Option 1: Clear all scope caches (if PSR-16 implementation supports namespaced clearing):

     public function writeEntity(\BO\Zmsentities\Scope $entity, $parentId)
     {
         self::$cache = [];
+        // Clear all scope-related cache entries
+        // Note: This requires a cache implementation that supports pattern-based clearing
+        // or you need to track all keys that need invalidation
         // ... rest of method
     }

Option 2: Invalidate specific known keys:

     public function updateEntity($scopeId, \BO\Zmsentities\Scope $entity, $resolveReferences = 0)
     {
         self::$cache = [];
+        // Invalidate known cache keys for this scope
+        for ($i = 0; $i <= 2; $i++) {
+            App::$cache->delete("scope-$scopeId-$i");
+        }
         // ... rest of method
     }

Option 3: Implement a cache tagging strategy using Symfony Cache's tag-aware adapters (see learnings), allowing you to tag all scope-related cache entries and invalidate them by tag.

♻️ Duplicate comments (1)
zmsticketprinter/templates/page/process.twig (1)

22-22: Verify backend provides process.queue.number.

Same concern as in print.twig: ensure the backend correctly populates process.queue.number before this change goes live.

🧹 Nitpick comments (18)
zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)

37-48: Consider consolidating access control logic.

The code applies access filtering twice: once via withAccessByWorkstation() (line 37) and again through manual iteration (lines 39-48). If both filters serve the same purpose, consider consolidating them into a single method to improve maintainability.

zmsdb/src/Zmsdb/DayOff.php (2)

70-75: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) unnecessarily checks whether the cache object exists before checking the retrieved data. Since App::$cache->get() is always called (line 72), the cache must already be initialized. Simplify to:

-        $data = App::$cache->get($cacheKey);
-        if (App::$cache && !empty($data)) {
-            return $data;
-        }
+        $data = App::$cache->get($cacheKey);
+        if (!empty($data)) {
+            return $data;
+        }

This pattern appears consistently across multiple files in the PR (Closure, Department, Config, etc.)—consider applying this simplification codebase-wide for consistency.


68-68: Consider adding a $disableCache parameter for consistency.

Other cached read methods in this PR (e.g., Department::readEntity, Scope::readEntity, Config::readEntity) include an optional $disableCache parameter to allow callers to bypass the cache when needed. Adding this parameter here would align with the broader caching pattern and provide flexibility for scenarios requiring fresh data (e.g., after writes, in tests, or when cache invalidation is complex).

zmsdb/src/Zmsdb/Department.php (1)

29-34: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant because App::$cache->get() is called unconditionally on line 30. Simplify to:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

This simplification applies across the codebase—see also DayOff.php, Closure.php, Config.php, Provider.php, Source.php, Request.php, and Cluster.php.

zmsdb/src/Zmsdb/Closure.php (2)

14-19: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) unnecessarily checks whether the cache object exists. Since App::$cache->get() is called unconditionally (line 16), simplify to:

-        $data = App::$cache->get($cacheKey);
-        if (App::$cache && !empty($data)) {
-            return $data;
-        }
+        $data = App::$cache->get($cacheKey);
+        if (!empty($data)) {
+            return $data;
+        }

12-12: Consider adding a $disableCache parameter for consistency.

Other cached read methods in this PR include an optional $disableCache parameter to allow bypassing the cache when needed. Adding this parameter would align with the caching pattern used in Department::readEntity, Scope::readEntity, Config::readEntity, and others.

zmsdb/src/Zmsdb/Config.php (2)

17-22: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant. Since App::$cache->get() is always called (line 18), simplify to:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

32-36: Verify cache invalidation timing in updateEntity.

The cache is deleted on line 34, but readEntity() is called on line 36 without $disableCache = true. Although the cache was just deleted, explicitly passing $disableCache = true makes the intent clearer and avoids a redundant cache lookup:

-        App::$cache->delete('config');
-
-        $compareEntity = $this->readEntity();
+        App::$cache->delete('config');
+
+        $compareEntity = $this->readEntity($disableCache = true);

Then at line 60:

-        return ($result) ? $this->readEntity() : null;
+        return ($result) ? $this->readEntity($disableCache = false) : null;

This ensures the final return uses the fresh cached value after all updates are complete.

zmsdb/src/Zmsdb/Provider.php (2)

15-20: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

31-31: Cache is written even when $disableCache is true.

Line 31 unconditionally caches the result, even when the caller passed $disableCache = true. While this may be intentional (read bypasses cache, but write still populates it), it's inconsistent with the typical interpretation of $disableCache. Consider guarding the cache write:

         $provider = $this->fetchOne($query, new Entity());
-        App::$cache->set($cacheKey, $provider);
+        if (!$disableCache) {
+            App::$cache->set($cacheKey, $provider);
+        }
         return $provider;

This pattern appears in multiple files (Request, Source, Cluster)—apply consistently if you adopt this approach.

zmsdb/src/Zmsdb/Source.php (2)

29-34: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

47-47: Cache is written even when $disableCache is true.

Line 47 unconditionally caches the result, even when $disableCache = true. Consider guarding the cache write:

         $entity = $this->readResolvedReferences($entity, $resolveReferences);
-
-        App::$cache->set($cacheKey, $entity);
+        
+        if (!$disableCache) {
+            App::$cache->set($cacheKey, $entity);
+        }
         return $entity;

See also Provider.php, Request.php, and Cluster.php for the same pattern.

zmsdb/src/Zmsdb/Request.php (2)

19-24: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

39-39: Cache is written even when $disableCache is true.

Line 39 unconditionally caches the result, even when $disableCache = true. Consider guarding the cache write for consistency:

         if (! $request->hasId()) {
             throw new Exception\Request\RequestNotFound("Could not find request with ID $source/$requestId");
         }
-
-        App::$cache->set($cacheKey, $request);
-
+        
+        if (!$disableCache) {
+            App::$cache->set($cacheKey, $request);
+        }
+        
         return $request;
zmsdb/src/Zmsdb/Cluster.php (2)

30-35: Simplify the cache check—App::$cache is redundant.

The condition if (App::$cache && !empty($data)) is redundant:

         $cacheKey = "cluster-$itemId-$resolveReferences";
         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

47-48: Cache is written even when $disableCache is true.

Line 48 unconditionally caches the result, even when $disableCache = true. Consider guarding the cache write:

         $cluster = $this->readResolvedReferences($cluster, $resolveReferences);
-        App::$cache->set($cacheKey, $cluster);
-
+        if (!$disableCache) {
+            App::$cache->set($cacheKey, $cluster);
+        }
+        
         return $cluster;
zmsdb/src/Zmsdb/Scope.php (2)

23-23: Use consistent cache key naming conventions.

The cache keys use different naming patterns:

  • Line 23: "scope-$scopeId-$resolveReferences" (short, entity-type prefix)
  • Line 95: "scopeReadByProviderId-$providerId-$resolveReferences" (long, method-name prefix)

Inconsistent naming makes cache key management harder and increases the risk of collisions.

Standardize to a consistent pattern. For example, use entity-operation format:

-        $cacheKey = "scope-$scopeId-$resolveReferences";
+        $cacheKey = "scope-byId-$scopeId-$resolveReferences";
-        $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
+        $cacheKey = "scope-byProviderId-$providerId-$resolveReferences";

Or adopt a structured format like {entity}:{operation}:{params}:

$cacheKey = "scope:byId:$scopeId:$resolveReferences";
$cacheKey = "scope:byProviderId:$providerId:$resolveReferences";

Also applies to: 95-95


21-44: Add error handling for cache operations.

Per PSR-16, cache implementations MUST throw Psr\SimpleCache\InvalidArgumentException for invalid keys and MAY throw Psr\SimpleCache\CacheException on operational failures. Currently, no error handling is present—exceptions will propagate and potentially break request processing.

Wrap cache operations in try-catch blocks to gracefully degrade when caching fails:

         if (!$disableCache) {
+            try {
                 $data = App::$cache->get($cacheKey, false);
                 if ($data !== false) {
                     return $data;
                 }
+            } catch (\Psr\SimpleCache\CacheException $e) {
+                // Log the error but continue without cache
+                // Assuming a logger is available via dependency injection or App::$log
+            }
         }

Similarly for cache writes:

-            App::$cache->set($cacheKey, $result);
+            try {
+                App::$cache->set($cacheKey, $result);
+            } catch (\Psr\SimpleCache\CacheException $e) {
+                // Log the error but continue
+            }

Also applies to: 93-131

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1894045 and 7793786.

📒 Files selected for processing (19)
  • zmsapi/composer.json (1 hunks)
  • zmsapi/src/Zmsapi/Application.php (3 hunks)
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1 hunks)
  • zmsdb/composer.json (1 hunks)
  • zmsdb/src/Zmsdb/Application.php (3 hunks)
  • zmsdb/src/Zmsdb/Calldisplay.php (1 hunks)
  • zmsdb/src/Zmsdb/Closure.php (3 hunks)
  • zmsdb/src/Zmsdb/Cluster.php (3 hunks)
  • zmsdb/src/Zmsdb/Config.php (2 hunks)
  • zmsdb/src/Zmsdb/DayOff.php (3 hunks)
  • zmsdb/src/Zmsdb/Department.php (3 hunks)
  • zmsdb/src/Zmsdb/Provider.php (2 hunks)
  • zmsdb/src/Zmsdb/Request.php (3 hunks)
  • zmsdb/src/Zmsdb/Scope.php (5 hunks)
  • zmsdb/src/Zmsdb/Source.php (3 hunks)
  • zmsdb/src/Zmsdb/Useraccount.php (1 hunks)
  • zmsentities/src/Zmsentities/Collection/DepartmentList.php (1 hunks)
  • zmsticketprinter/templates/block/content/print.twig (1 hunks)
  • zmsticketprinter/templates/page/process.twig (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/composer.json
  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/composer.json
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Application.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Useraccount.php
  • zmsticketprinter/templates/page/process.twig
  • zmsticketprinter/templates/block/content/print.twig
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsentities/src/Zmsentities/Collection/DepartmentList.php
  • zmsapi/src/Zmsapi/Application.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Application.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Useraccount.php
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsentities/src/Zmsentities/Collection/DepartmentList.php
  • zmsapi/src/Zmsapi/Application.php
🧬 Code graph analysis (14)
zmsdb/src/Zmsdb/Request.php (6)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Source.php (2)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (4)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-51)
  • updateEntity (378-389)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-49)
  • updateEntity (193-217)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Application.php (2)
zmsapi/src/Zmsapi/Application.php (5)
  • initializeCache (124-130)
  • validateCacheDirectory (132-141)
  • setupCache (143-147)
  • initialize (149-152)
  • Application (25-153)
zmscitizenapi/src/Zmscitizenapi/Application.php (5)
  • initializeCache (108-114)
  • validateCacheDirectory (146-155)
  • setupCache (157-161)
  • initialize (63-70)
  • Application (16-208)
zmsdb/src/Zmsdb/Scope.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Provider.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Provider.php (1)
  • Provider (5-104)
zmsdb/src/Zmsdb/Query/Provider.php (1)
  • Provider (5-86)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Cluster.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-49)
  • readResolvedReferences (51-62)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (2)
  • readEntity (21-45)
  • readResolvedReferences (47-56)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-49)
  • readResolvedReferences (79-94)
zmsdb/src/Zmsdb/Closure.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Department.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Useraccount.php (2)
zmsentities/src/Zmsentities/Schema/Entity.php (1)
  • toProperty (264-267)
zmsentities/src/Zmsentities/Collection/Base.php (1)
  • addEntity (109-113)
zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)
zmsdb/src/Zmsdb/Useraccount.php (1)
  • readSearchByDepartmentId (276-296)
zmsdb/src/Zmsdb/DayOff.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Collection/DepartmentList.php (1)
zmsentities/src/Zmsentities/Collection/Base.php (1)
  • addEntity (109-113)
zmsapi/src/Zmsapi/Application.php (2)
zmsdb/src/Zmsdb/Application.php (5)
  • initializeCache (71-77)
  • validateCacheDirectory (79-88)
  • setupCache (90-94)
  • initialize (96-99)
  • Application (19-100)
zmscitizenapi/src/Zmscitizenapi/Application.php (5)
  • initializeCache (108-114)
  • validateCacheDirectory (146-155)
  • setupCache (157-161)
  • initialize (63-70)
  • Application (16-208)
🪛 GitHub Actions: Combined Workflow
zmsapi/composer.json

[error] 1-1: Git detected dubious ownership for repository; add safe.directory for path '/__w/eappointment/eappointment'. Composer could not detect the root package (eappointment/zmsapi); defaulting to version 1.0.0.

zmsdb/src/Zmsdb/Calldisplay.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Request.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Source.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/composer.json

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version


[warning] 1-1: Warning: Lock file is not up to date with composer.json. It is recommended to run 'composer update' to refresh dependencies.

zmsdb/src/Zmsdb/Config.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Application.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Scope.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Provider.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Cluster.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Closure.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Department.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsdb/src/Zmsdb/Useraccount.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php

[error] 1-1: Git detected dubious ownership for repository; add safe.directory for path '/__w/eappointment/eappointment'. Composer could not detect the root package (eappointment/zmsapi); defaulting to version 1.0.0.

zmsdb/src/Zmsdb/DayOff.php

[warning] 1-1: Composer could not detect the root package (eappointment/zmsdb) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version

zmsapi/src/Zmsapi/Application.php

[error] 1-1: Git detected dubious ownership for repository; add safe.directory for path '/__w/eappointment/eappointment'. Composer could not detect the root package (eappointment/zmsapi); defaulting to version 1.0.0.

🔇 Additional comments (3)
zmsticketprinter/templates/block/content/print.twig (1)

4-4: No action needed; process.queue.number is populated in the backend and mirrors the previous displayNumber logic.

zmsticketprinter/templates/page/process.twig (1)

64-64: No action needed: waitingNumber already maps to the queue index

The Notification endpoint in zmsticketprinter/src/Zmsticketprinter/Notification.php reads waitingNumber to fetch /scope/{id}/queue/{waitingNumber}. Tests (NotificationByScopeTest, NotificationFailedTest) post a numeric waitingNumber matching process.queue.number. This change aligns with existing endpoint logic.

zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)

36-36: Verify removal of withLessData for department search

Removing withLessData() at UseraccountSearchByDepartment returns full Useraccount objects. Confirm that this aligns with the caching strategy, that downstream consumers and existing tests handle the larger payload without performance regressions or failures.

Comment on lines +42 to 44
"symfony/cache": "^6.0",
"psr/simple-cache": "^3.0",
"monolog/monolog": "^2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Target a supported symfony/cache release.

symfony/cache 6.0 reached end of support in January 2023, so new security fixes no longer land on that branch. Please bump the constraint to a maintained line (e.g. ^6.4 LTS or ^7.0) before shipping the cache integration.

🤖 Prompt for AI Agents
In zmsapi/composer.json around lines 42 to 44, the dependency constraint for
"symfony/cache" is set to "^6.0" which is EOL and must be updated; change the
constraint to a supported release (for example "^6.4" for the LTS line or
"^7.0") and run composer update to refresh the lockfile and verify compatibility
with other packages (adjust any code if new major version BC breaks appear).

$parameters = $request->getParams();

/** @var Useraccount $useraccount */
$useraccountList = new Collection();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused initialization.

The collection created on line 35 is immediately overwritten on line 36, making this line dead code.

Apply this diff to remove the unused initialization:

 /** @var Useraccount $useraccount */
-$useraccountList = new Collection();
 $useraccountList = (new Useraccount())->readSearchByDepartmentId($department->id, $parameters, $resolveReferences);
🤖 Prompt for AI Agents
In zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php around line 35, the code
creates a new Collection assigned to $useraccountList which is immediately
overwritten on the next line; remove the unused initialization on line 35 so
$useraccountList is only assigned the actual value on line 36, eliminating the
dead code.

Comment on lines +37 to +38
"symfony/cache": "^6.0",
"psr/simple-cache": "^3.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a maintained symfony/cache version.

Version 6.0 is out of support; it no longer receives security updates. Please depend on a supported branch (^6.4 LTS or ^7.0) to keep the new cache layer within a maintained release window.

🤖 Prompt for AI Agents
In zmsdb/composer.json around lines 37 to 38, the dependency on "symfony/cache":
"^6.0" targets an unsupported release; update the constraint to a maintained
branch such as "^6.4" or "^7.0" (choose the one compatible with your codebase),
keep "psr/simple-cache": "^3.0" compatibility, then run composer update and run
your test suite or static checks to ensure no breaking changes.

Comment on lines +80 to +87
{
if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
}

if (!is_writable(self::$CACHE_DIR)) {
throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Harden cache-directory creation against race conditions

Two workers hitting initialize() concurrently can both see the cache directory missing, one succeeds, the other hits mkdir() after the directory already exists, gets false, and we throw. This causes spurious runtime failures on first requests. Add a second is_dir guard after mkdir() (or otherwise handle the EEXIST case) before raising.

Apply this diff:

-        if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
-            throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
-        }
+        if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true) && !is_dir(self::$CACHE_DIR)) {
+            throw new \RuntimeException(sprintf(
+                'Cache directory "%s" could not be created',
+                self::$CACHE_DIR
+            ));
+        }
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Application.php around lines 80 to 87, the cache-directory
creation is racy: two processes can both observe the directory missing and the
second mkdir() can fail when the first created it, causing a thrown
RuntimeException. Fix by re-checking is_dir() immediately after a failed mkdir()
and only throw if the directory still does not exist (or alternatively inspect
errno for EEXIST); then proceed to the is_writable() check as before. Ensure the
code preserves the original permissions attempt and only treats mkdir failure as
fatal if the directory truly does not exist.

Comment on lines 155 to 161
if (0 < $resolveReferences && $entity->toProperty()->id->get()) {
$entity->departments = $this->readAssignedDepartmentList(
$entity,
$resolveReferences - 1
);
}
$collection->addEntity($entity);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove code duplication and ensure consistent reference resolution.

This code duplicates the logic from readResolvedReferences (lines 39-41) and creates inconsistent behavior compared to similar methods like readCollectionByDepartmentId (line 139).

Issues:

  1. DRY violation: The department population logic is duplicated here instead of reusing readResolvedReferences.
  2. Inconsistent depth: Passing $resolveReferences - 1 to readAssignedDepartmentList differs from readResolvedReferences which passes $resolveReferences, causing departments to be resolved one level shallower than in other methods.
  3. Maintenance burden: If readResolvedReferences is extended with additional logic, this method won't benefit from those changes.

Refactor to use the standard resolution path:

         if (count($result)) {
             foreach ($result as $entity) {
-                if (0 < $resolveReferences && $entity->toProperty()->id->get()) {
-                    $entity->departments = $this->readAssignedDepartmentList(
-                        $entity,
-                        $resolveReferences - 1
-                    );
-                }
-                $collection->addEntity($entity);
+                $collection->addEntity($this->readResolvedReferences($entity, $resolveReferences));
             }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (0 < $resolveReferences && $entity->toProperty()->id->get()) {
$entity->departments = $this->readAssignedDepartmentList(
$entity,
$resolveReferences - 1
);
}
$collection->addEntity($entity);
if (count($result)) {
foreach ($result as $entity) {
$collection->addEntity($this->readResolvedReferences($entity, $resolveReferences));
}
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Useraccount.php around lines 155-161, remove the duplicated
department population block and instead invoke the existing resolution helper so
department references are handled consistently; replace the if-block that calls
readAssignedDepartmentList with a call to $this->readResolvedReferences($entity,
$resolveReferences) (matching the pattern used elsewhere) so you reuse the
shared logic and pass $resolveReferences (not $resolveReferences - 1) to
maintain consistent resolution depth.

if ($department->hasAccess($useraccount)) {
if ($useraccount->rights['organisation']) {
return clone $this;
return $this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Returning $this breaks immutability and allows unintended mutations.

Returning the original collection reference ($this) instead of a clone violates the immutability pattern followed by other methods in this class (e.g., withMatchingScopes at line 73 clones entities). This allows callers to modify the original collection, potentially causing side effects elsewhere in the codebase.

Additionally, the early return inside the loop is inefficient—if organisation rights grant full access, checking before the loop would be cleaner.

Apply this diff to check organisation rights before the loop and return a reference safely, or return a clone if immutability is required:

 public function withAccess(\BO\Zmsentities\Useraccount $useraccount)
 {
+    // Early check for organisation rights - return full list if granted
+    if ($useraccount->rights['organisation']) {
+        return $this; // or: return clone $this; if immutability is required
+    }
+    
     $list = new static();
     foreach ($this as $department) {
         if ($department->hasAccess($useraccount)) {
-            if ($useraccount->rights['organisation']) {
-                return $this;
-            }
-            $list->addEntity($department);
+            $list->addEntity(clone $department);
         }
     }
     return $list;
 }
🤖 Prompt for AI Agents
In zmsentities/src/Zmsentities/Collection/DepartmentList.php around line 61, the
method currently returns $this which breaks the class's immutability contract
and performs an early return inside the loop; update the method to check
organisation-level rights once before looping (so if organisation grants full
access you can short-circuit) and return a clone of the collection (not $this)
to preserve immutability consistent with withMatchingScopes at line 73; ensure
any early-return branch returns a cloned instance and not the original object.

return $this;
}
$list->addEntity(clone $department);
$list->addEntity($department);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Not cloning departments breaks the immutability pattern.

Other methods in this class (e.g., withMatchingScopes at line 73, withOutClusterDuplicates at line 15) create new instances or clones before adding to result collections. Adding the original department reference here means modifications to departments in the returned list will affect the source collection.

Apply this diff to clone departments before adding them:

-            $list->addEntity($department);
+            $list->addEntity(clone $department);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$list->addEntity($department);
$list->addEntity(clone $department);
🤖 Prompt for AI Agents
In zmsentities/src/Zmsentities/Collection/DepartmentList.php around line 63 the
code adds the original $department to the new list which breaks the class's
immutability pattern; change the call to add a cloned instance instead (use
PHP's clone operator) so the returned collection contains clones: replace adding
the original $department with adding clone $department to ensure modifications
to the returned list don't affect the source collection and stay consistent with
other methods.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
zmsdb/src/Zmsdb/Closure.php (1)

14-35: Guard cache access before dereferencing.

App::$cache is nullable. Calling App::$cache->get() / ->set() without first verifying the instance will trigger a fatal error whenever the cache isn’t initialized (e.g., CLI scripts, tests). Add an early if (App::$cache && !$disableCache) { ... } guard around both the read and write paths.

zmsdb/src/Zmsdb/Source.php (1)

25-48: Add cache null-check and invalidate on writes.

Two blockers here:

  1. App::$cache is nullable; calling ->get() / ->set() without checking will crash if the cache isn’t initialized (same scenario as in other repositories). Wrap both operations in if (App::$cache && !$disableCache) { … }.
  2. writeEntity() / writeDeleteBySource() never clear this cache key, so updates can return stale data straight from cache. Ensure you delete() (or bypass caching) before re-reading.

Please address both to keep the cache safe and coherent.

zmsdb/src/Zmsdb/Request.php (1)

15-40: Protect cache access and handle invalidation.

App::$cache may be null; invoking ->get() or ->set() without checking will fatally error when caching hasn’t been bootstrapped. Please guard both calls. Also, mutations (writeEntity, writeDeleteEntity, etc.) don’t purge the cached request, so consumers can read stale entries. Make sure to delete the corresponding cache key whenever the request changes.

zmsdb/src/Zmsdb/Department.php (1)

22-47: Guard cache usage and invalidate external cache.

Like the other services, App::$cache can be null; the current code calls ->get() / ->set() without verifying it. Add a guard to avoid fatal errors when the cache isn’t initialised. In addition, write/update/delete paths reset only self::$departmentCache, leaving App::$cache with stale department instances. Make sure to delete() the PSR-16 cache entry (or skip cached reads) when mutating data.

zmsdb/src/Zmsdb/Cluster.php (1)

357-368: Cache invalidation missing on write operations.

The writeEntity and updateEntity methods do not invalidate the cache before writing. This could lead to serving stale cached data after entities are modified. Consider adding cache invalidation at the start of these methods.

For writeEntity, apply this diff:

     public function writeEntity(\BO\Zmsentities\Cluster $entity)
     {
+        // Invalidate cache for this cluster (we don't know the ID yet, but after insert we should invalidate)
         $query = new Query\Cluster(Query\Base::INSERT);
         $values = $query->reverseEntityMapping($entity);
         $query->addValues($values);
         $this->writeItem($query);
         $lastInsertId = $this->getWriter()->lastInsertId();
         if ($entity->toProperty()->scopes->isAvailable()) {
             $this->writeAssignedScopes($lastInsertId, $entity->scopes);
         }
+        // Note: readEntity will populate cache with fresh data
         return $this->readEntity($lastInsertId, 1);
     }

For updateEntity, apply this diff:

     public function updateEntity($clusterId, \BO\Zmsentities\Cluster $entity)
     {
+        // Invalidate cache for all resolve levels of this cluster
+        App::$cache->delete("cluster-$clusterId-0");
+        App::$cache->delete("cluster-$clusterId-1");
+        App::$cache->delete("cluster-$clusterId-2");
         $query = new Query\Cluster(Query\Base::UPDATE);
         $query->addConditionClusterId($clusterId);
         $values = $query->reverseEntityMapping($entity);
         $query->addValues($values);
         $this->writeItem($query);
         if ($entity->toProperty()->scopes->isAvailable()) {
             $this->writeAssignedScopes($clusterId, $entity->scopes);
         }
         return $this->readEntity($clusterId, 1);
     }

Alternatively, consider implementing a helper method to invalidate all cache entries for a given cluster across all resolution levels.

Also applies to: 378-389

zmsdb/src/Zmsdb/Provider.php (2)

22-33: Cache set should occur after validation.

The cache is set on line 31 without checking if the provider entity is valid. If fetchOne returns an empty or null provider, this will cache invalid data.

Apply this diff:

         $this->testSource($source);
         $query = new Query\Provider(Query\Base::SELECT);
         $query
             ->setResolveLevel($resolveReferences)
             ->addEntityMapping()
             ->addResolvedReferences($resolveReferences)
             ->addConditionProviderSource($source)
             ->addConditionProviderId($providerId);
         $provider = $this->fetchOne($query, new Entity());
-        App::$cache->set($cacheKey, $provider);
+        if ($provider && $provider->hasId()) {
+            App::$cache->set($cacheKey, $provider);
+        }
         return $provider;
     }

68-99: Cache invalidation missing on write and delete operations.

The writeEntity and writeDeleteEntity methods do not invalidate the cache. This will cause stale data to be served after modifications.

For writeEntity, apply this diff to invalidate cache for both the old and new provider:

     public function writeEntity(Entity $entity)
     {
         $this->writeDeleteEntity($entity->getId(), $entity->getSource());
+        // Invalidate cache for all resolve levels
+        $source = $entity->getSource();
+        $id = $entity->getId();
+        App::$cache->delete("provider-$source-$id-0");
+        App::$cache->delete("provider-$source-$id-1");
+        App::$cache->delete("provider-$source-$id-2");
         $contact =  $entity->getContact();
         // ... rest of method
     }

For writeDeleteEntity, apply this diff:

     public function writeDeleteEntity($providerId, $source)
     {
+        // Invalidate cache for all resolve levels
+        App::$cache->delete("provider-$source-$providerId-0");
+        App::$cache->delete("provider-$source-$providerId-1");
+        App::$cache->delete("provider-$source-$providerId-2");
         $query = new Query\Provider(Query\Base::DELETE);
         $query->addConditionProviderId($providerId);
         $query->addConditionProviderSource($source);
         return $this->deleteItem($query);
     }

Also applies to: 146-152

zmsdb/src/Zmsdb/Scope.php (1)

21-45: Inconsistent two-level cache implementation.

This method uses both in-memory (self::$cache) and persistent (App::$cache) caching, but only the in-memory cache is checked after the persistent cache miss (line 32). This creates an inconsistent caching hierarchy. More critically, when the cache is cleared in write operations (lines 411, 432, 487, 579), only self::$cache is cleared but App::$cache is not invalidated, causing stale persistent cache to be served.

Consider one of these approaches:

Option 1: Remove dual caching and use only App::$cache (recommended)

     public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
     {
         $cacheKey = "scope-$scopeId-$resolveReferences";

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }

-        if ($disableCache || ! array_key_exists($cacheKey, self::$cache)) {
-            $query = new Query\Scope(Query\Base::SELECT);
-            $query->addEntityMapping()
-                ->addResolvedReferences($resolveReferences)
-                ->addConditionScopeId($scopeId);
-            $scope = $this->fetchOne($query, new Entity());
-            if (! $scope->hasId()) {
-                return null;
-            }
-            self::$cache[$cacheKey] = $this->readResolvedReferences($scope, $resolveReferences);
-            App::$cache->set($cacheKey, self::$cache[$cacheKey]);
-        }
-        return self::$cache[$cacheKey];
+        $query = new Query\Scope(Query\Base::SELECT);
+        $query->addEntityMapping()
+            ->addResolvedReferences($resolveReferences)
+            ->addConditionScopeId($scopeId);
+        $scope = $this->fetchOne($query, new Entity());
+        if (! $scope->hasId()) {
+            return null;
+        }
+        $scope = $this->readResolvedReferences($scope, $resolveReferences);
+        App::$cache->set($cacheKey, $scope);
+        return $scope;
     }

Option 2: Invalidate both caches on writes

In methods at lines 411, 432, 487, and 579, also clear App::$cache:

     public function writeEntity(\BO\Zmsentities\Scope $entity, $parentId)
     {
         self::$cache = [];
+        // Invalidate persistent cache - note: we don't know the exact keys, so this is incomplete
+        // A better approach would be to use cache tags or a helper method
         $query = new Query\Scope(Query\Base::INSERT);
         // ... rest of method
     }

Note: Option 2 is incomplete because we don't know all the cache keys to invalidate. Option 1 is cleaner and recommended.

♻️ Duplicate comments (1)
zmsapi/composer.json (1)

42-43: See prior note on symfony/cache version.

The concern about locking to the unmaintained 6.0 branch applies here as well.

🧹 Nitpick comments (7)
zmsdb/src/Zmsdb/Cluster.php (1)

29-35: Simplify the cache existence check.

The condition if (App::$cache && !empty($data)) is redundant. If App::$cache is null, the get() call on line 31 would have already failed. Consider simplifying to just check if data is not empty.

Apply this diff:

         $cacheKey = "cluster-$itemId-$resolveReferences";
         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }
zmsdb/src/Zmsdb/Provider.php (1)

15-20: Simplify the cache existence check.

Similar to other files, the condition if (App::$cache && !empty($data)) is redundant. If App::$cache is null, the get() call would fail.

Apply this diff:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }
zmsdb/src/Zmsdb/Config.php (1)

17-22: Simplify the cache existence check.

The condition if (App::$cache && !empty($data)) is redundant across the codebase.

Apply this diff:

         $cacheKey = "config";
         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }
zmsdb/src/Zmsdb/Scope.php (1)

97-102: Simplify the cache existence check.

Same redundant pattern as other files.

Apply this diff:

         if (!$disableCache) {
             $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
+            if (!empty($data)) {
                 return $data;
             }
         }
zmsdb/src/Zmsdb/Application.php (3)

71-77: Consider externalizing cache configuration.

The cache directory defaults to __DIR__ . '/cache' (inside the source directory) and TTL defaults to 3600 seconds. For production deployments, the cache should be in a dedicated location (e.g., /var/cache/zmsdb).

While environment variables are supported, consider documenting these requirements prominently or providing a configuration file. Also consider making the defaults more production-ready:

     private static function initializeCache(): void
     {
-        self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
+        self::$CACHE_DIR = getenv('CACHE_DIR') ?: '/var/cache/zmsdb';
         self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
         self::validateCacheDirectory();
         self::setupCache();
     }

Also, document these environment variables in your deployment documentation or README.


79-88: Add error handling for cache directory validation.

The directory creation and permission checks should have more robust error handling, especially for production environments. Consider logging the error before throwing the exception.

As per coding guidelines

     private static function validateCacheDirectory(): void
     {
         if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
+            $error = error_get_last();
+            // If logging is available at this point, log the error
             throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
         }

         if (!is_writable(self::$CACHE_DIR)) {
+            // If logging is available at this point, log the error
             throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
         }
     }

Note: Proper Monolog logging should be added here if the logger is initialized before cache initialization.


71-99: Code duplication across Application classes.

The cache initialization logic (initializeCache, validateCacheDirectory, setupCache) is duplicated across multiple Application classes (zmsdb, zmsapi, zmscitizenapi as seen in relevant snippets). This violates the DRY principle.

Consider extracting this logic into a shared trait or abstract base class:

trait CacheInitializationTrait
{
    public static ?CacheInterface $cache = null;
    public static string $CACHE_DIR;
    public static int $SOURCE_CACHE_TTL;

    private static function initializeCache(string $namespace): void
    {
        self::$CACHE_DIR = getenv('CACHE_DIR') ?: '/var/cache/' . $namespace;
        self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
        self::validateCacheDirectory();
        self::setupCache($namespace);
    }

    private static function validateCacheDirectory(): void
    {
        if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
            throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
        }

        if (!is_writable(self::$CACHE_DIR)) {
            throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
        }
    }

    private static function setupCache(string $namespace): void
    {
        $psr6 = new FilesystemAdapter(namespace: $namespace, defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
        self::$cache = new Psr16Cache($psr6);
    }
}

Then use it in Application classes:

 class Application extends \BO\Slim\Application
 {
+    use CacheInitializationTrait;
+
     const IDENTIFIER = 'zms';
     const MODULE_NAME = 'zmsdb';
     // ... other constants

-    public static ?CacheInterface $cache = null;
-    public static string $CACHE_DIR;
-    public static int $SOURCE_CACHE_TTL;
-
-    private static function initializeCache(): void
-    {
-        self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
-        self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
-        self::validateCacheDirectory();
-        self::setupCache();
-    }
-
-    private static function validateCacheDirectory(): void
-    {
-        if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
-            throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
-        }
-
-        if (!is_writable(self::$CACHE_DIR)) {
-            throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
-        }
-    }
-
-    private static function setupCache(): void
-    {
-        $psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
-        self::$cache = new Psr16Cache($psr6);
-    }

     public static function initialize(): void
     {
-        self::initializeCache();
+        self::initializeCache('zmsdb');
     }
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1894045 and b9284af.

⛔ Files ignored due to path filters (2)
  • zmsapi/composer.lock is excluded by !**/*.lock
  • zmsdb/composer.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • zmsapi/composer.json (1 hunks)
  • zmsapi/src/Zmsapi/Application.php (3 hunks)
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1 hunks)
  • zmsdb/composer.json (1 hunks)
  • zmsdb/src/Zmsdb/Application.php (3 hunks)
  • zmsdb/src/Zmsdb/Calldisplay.php (1 hunks)
  • zmsdb/src/Zmsdb/Closure.php (3 hunks)
  • zmsdb/src/Zmsdb/Cluster.php (3 hunks)
  • zmsdb/src/Zmsdb/Config.php (2 hunks)
  • zmsdb/src/Zmsdb/DayOff.php (3 hunks)
  • zmsdb/src/Zmsdb/Department.php (3 hunks)
  • zmsdb/src/Zmsdb/Provider.php (2 hunks)
  • zmsdb/src/Zmsdb/Request.php (3 hunks)
  • zmsdb/src/Zmsdb/Scope.php (5 hunks)
  • zmsdb/src/Zmsdb/Source.php (3 hunks)
  • zmsdb/src/Zmsdb/Useraccount.php (1 hunks)
  • zmsentities/src/Zmsentities/Collection/DepartmentList.php (1 hunks)
  • zmsticketprinter/templates/block/content/print.twig (1 hunks)
  • zmsticketprinter/templates/page/process.twig (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsticketprinter/templates/block/content/print.twig
  • zmsticketprinter/templates/page/process.twig
  • zmsdb/composer.json
  • zmsentities/src/Zmsentities/Collection/DepartmentList.php
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Application.php
  • zmsapi/src/Zmsapi/Application.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsapi/composer.json
  • zmsdb/src/Zmsdb/Useraccount.php
  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Provider.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsentities/src/Zmsentities/Collection/DepartmentList.php
  • zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Application.php
  • zmsapi/src/Zmsapi/Application.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Useraccount.php
  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Provider.php
🧬 Code graph analysis (14)
zmsentities/src/Zmsentities/Collection/DepartmentList.php (1)
zmsentities/src/Zmsentities/Collection/Base.php (1)
  • addEntity (109-113)
zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)
zmsdb/src/Zmsdb/Useraccount.php (1)
  • readSearchByDepartmentId (276-296)
zmsdb/src/Zmsdb/Request.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Scope.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/DayOff.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Department.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Application.php (2)
zmsapi/src/Zmsapi/Application.php (5)
  • initializeCache (124-130)
  • validateCacheDirectory (132-141)
  • setupCache (143-147)
  • initialize (149-152)
  • Application (25-153)
zmscitizenapi/src/Zmscitizenapi/Application.php (5)
  • initializeCache (108-114)
  • validateCacheDirectory (146-155)
  • setupCache (157-161)
  • initialize (63-70)
  • Application (16-208)
zmsapi/src/Zmsapi/Application.php (2)
zmsdb/src/Zmsdb/Application.php (5)
  • initializeCache (71-77)
  • validateCacheDirectory (79-88)
  • setupCache (90-94)
  • initialize (96-99)
  • Application (19-100)
zmscitizenapi/src/Zmscitizenapi/Application.php (5)
  • initializeCache (108-114)
  • validateCacheDirectory (146-155)
  • setupCache (157-161)
  • initialize (63-70)
  • Application (16-208)
zmsdb/src/Zmsdb/Config.php (2)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Source.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Cluster.php (4)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-49)
  • readResolvedReferences (51-62)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-49)
  • readResolvedReferences (79-94)
zmsdb/src/Zmsdb/Useraccount.php (2)
zmsentities/src/Zmsentities/Schema/Entity.php (1)
  • toProperty (264-267)
zmsentities/src/Zmsentities/Collection/Base.php (1)
  • addEntity (109-113)
zmsdb/src/Zmsdb/Closure.php (1)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Provider.php (4)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsticketprinter, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsslim, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsstatistic, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsdb, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsdldb, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsentities, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsclient, 8.3)
  • GitHub Check: call-php-unit-tests / module-test (zmscitizenapi, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmscitizenapi, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmscalldisplay, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsadmin, 8.3)
  • GitHub Check: call-php-unit-tests / zmsapi-test
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (5)
zmsticketprinter/templates/page/process.twig (1)

22-22: Verify queue number format and downstream handling

  • Notification handler now uses waitingNumber (validated as numeric; correctly wired).
  • Confirm process.queue.number matches the previous process.displayNumber format/type and update any validation or logic still relying on displayNumber.
zmsticketprinter/templates/block/content/print.twig (1)

4-4: Confirm process.queue.number is provided and non-null
Ensure the waitingnumber API response includes a valid queue.number field and that Twig renders it correctly, adding a fallback if it can be null.

zmsdb/src/Zmsdb/Config.php (1)

32-61: LGTM! Cache invalidation is properly implemented.

The cache invalidation on line 34 correctly clears the cached config before updating, and the method returns freshly cached data from readEntity(). This ensures cache consistency.

zmsapi/src/Zmsapi/UseraccountSearchByDepartment.php (1)

36-36: LGTM! Cleaner data resolution approach.

Moving the data resolution control from post-processing (->withLessData()) to a method parameter ($resolveReferences) improves separation of concerns by handling data shaping within the data access layer.

zmsdb/src/Zmsdb/DayOff.php (1)

5-5: LGTM!

The import alias is necessary for accessing the cache infrastructure introduced in this PR.

Comment on lines +37 to +38
"symfony/cache": "^6.0",
"psr/simple-cache": "^3.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Upgrade symfony/cache to a maintained branch.

symfony/cache constrained to ^6.0 pulls an EOL release line (ended support Jan 2023). Please target a supported branch (e.g., 6.4 LTS or 7.x) so we keep receiving security patches. Based on learnings

🤖 Prompt for AI Agents
In zmsdb/composer.json around lines 37 to 38, the symfony/cache dependency is
pinned to the EOL ^6.0 branch; update the constraint to a maintained release
(for example "^6.4" for the current LTS or "^7.0" if you prefer the next major),
then run composer update symfony/cache (or composer update) to refresh
composer.lock and verify the updated versions; run your test suite and fix any
deprecations or breaking changes introduced by the newer symfony/cache release.

Comment on lines +10 to +12
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify Symfony Cache version compatibility.

The PR uses symfony/cache ^6.0 according to the learnings. Note that Symfony 6.0 reached end-of-life in January 2023 and no longer receives security updates.

Based on learnings

Also applies to: 32-35


🌐 Web query:

What is the latest stable version of Symfony Cache component, and what are the security implications of using symfony/cache 6.0?

💡 Result:

  • Latest stable symfony/cache: v7.3.4 (current Symfony stable 7.3 series). [1][2]

  • Security implications of using symfony/cache 6.0:

    • Symfony 6.0 is end-of-life and no longer receives security fixes (active/security support ended Jan 31, 2023). Running 6.0 means you will not get patched fixes for newly discovered vulnerabilities. [3][4]
    • Unpatched cache-related issues can expose sensitive data (e.g., cache poisoning or caching responses containing session/cookie data) and have led to real CVE-class issues in Symfony-based stacks. Using an EOL cache component therefore increases risk of cache poisoning, information disclosure, and related attacks. [5][6]
    • Recommendation: upgrade to a maintained release (e.g., Symfony 7.3.x or the 6.4 LTS) to receive security fixes, or backport/patch and isolate caching layers until you can upgrade. [2][3]

Sources:

  1. Packagist — symfony/cache (v7.3.4).
  2. Symfony releases / “7.3 maintained” (latest patch 7.3.4).
  3. endoflife.date / eol.support — Symfony 6.0 EOL and last patch dates (security support ended Jan 31, 2023).
  4. Ubuntu security notice summarizing Symfony vulnerabilities.
  5. CVE examples and writeups showing cache/session-related issues (e.g., CVE-2024-27917).

Upgrade symfony/cache to a maintained release

symfony/cache ^6.0 is end-of-life since Jan 31, 2023 and no longer receives security fixes—risking cache poisoning and data exposure. Bump the dependency to ^6.4 (LTS) or ^7.3.x (v7.3.4+) to restore security support.

Comment on lines +90 to +94
private static function setupCache(): void
{
$psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
self::$cache = new Psr16Cache($psr6);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty cache namespace creates collision risk.

The FilesystemAdapter is initialized with an empty namespace (namespace: ''). This means all modules using this cache (zmsdb, zmsapi, etc.) share the same cache namespace. If cache keys are not carefully prefixed (e.g., "cluster-", "scope-"), there's a risk of key collisions between modules.

Based on learnings

Apply this diff to namespace the cache by module:

     private static function setupCache(): void
     {
-        $psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
+        $psr6 = new FilesystemAdapter(namespace: 'zmsdb', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
         self::$cache = new Psr16Cache($psr6);
     }

Ensure other modules (zmsapi, zmscitizenapi) also use module-specific namespaces to avoid collisions.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static function setupCache(): void
{
$psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
self::$cache = new Psr16Cache($psr6);
}
private static function setupCache(): void
{
$psr6 = new FilesystemAdapter(namespace: 'zmsdb', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
self::$cache = new Psr16Cache($psr6);
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Application.php around lines 90-94, the FilesystemAdapter is
created with an empty namespace which risks cache key collisions across modules;
change the namespace argument to a module-specific value (for example "zmsdb" or
a constant like self::CACHE_NAMESPACE) so the adapter is initialized with
namespace: 'zmsdb' (or equivalent), keep defaultLifetime and directory as-is,
and ensure other modules (zmsapi, zmscitizenapi) are similarly updated to use
their own module-specific namespaces.

if ($department->hasAccess($useraccount)) {
if ($useraccount->rights['organisation']) {
return clone $this;
return $this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Returning $this breaks immutability pattern.

Returning the original instance instead of a new filtered list breaks the immutability pattern established by the with* method naming convention and other methods in this class (withOutClusterDuplicates, withMatchingScopes). This could lead to unintended mutations of the original list and potentially corrupt cached entities in the caching infrastructure introduced by this PR.

If you need to return all departments for organisation rights, consider:

-                    return $this;
+                    return clone $this;

Or, if the entire list should be returned without filtering (but maintaining the pattern):

 public function withAccess(\BO\Zmsentities\Useraccount $useraccount)
 {
+    if ($useraccount->rights['organisation']) {
+        return $this;
+    }
     $list = new static();
     foreach ($this as $department) {
         if ($department->hasAccess($useraccount)) {
-            if ($useraccount->rights['organisation']) {
-                return $this;
-            }
             $list->addEntity($department);
         }
     }
     return $list;
 }
🤖 Prompt for AI Agents
In zmsentities/src/Zmsentities/Collection/DepartmentList.php around line 61, the
method currently returns $this which breaks the immutability contract of the
with* methods; instead construct and return a new DepartmentList instance (or
clone populated via the existing factory/constructor) containing the appropriate
items (either the full list or the filtered subset) so the original list remains
unchanged and cached entity references are not mutated; ensure you copy any
internal metadata (scopes, clusters, etc.) into the new instance so behavior
remains identical except for the modified items.

return $this;
}
$list->addEntity(clone $department);
$list->addEntity($department);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent entity handling - consider cloning for immutability.

Adding the department directly without cloning is inconsistent with other methods in this class. Both withOutClusterDuplicates() (line 15) and withMatchingScopes() (line 73) clone entities before adding them to filtered lists. Not cloning risks mutations propagating back to cached entities.

For consistency with the class's immutability pattern, clone the department:

-                $list->addEntity($department);
+                $list->addEntity(clone $department);
🤖 Prompt for AI Agents
In zmsentities/src/Zmsentities/Collection/DepartmentList.php around line 63, the
code adds $department directly to the filtered list which is inconsistent with
other methods that clone entities first; change the code to clone the
$department (e.g., $departmentClone = clone $department) and add the clone to
the list so mutations on the filtered result cannot affect the original/cached
entity, keeping behavior consistent with withOutClusterDuplicates() and
withMatchingScopes().

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
zmsapi/src/Zmsapi/Application.php (4)

34-37: Consider dependency injection for cache in future refactoring.

While the static properties align with the existing class design, they break the Dependency Inversion Principle and make unit testing more difficult. For future improvements, consider using instance-based dependency injection to decouple the cache from global state. As per coding guidelines.


132-141: Consider adding logging for cache directory operations.

While the error handling is correct, adding logging for directory creation and validation failures would improve observability. As per coding guidelines.

Example enhancement:

private static function validateCacheDirectory(): void
{
    if (!is_dir(self::$CACHE_DIR)) {
        if (!mkdir(self::$CACHE_DIR, 0750, true)) {
            // Log error if logger is available
            throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
        }
        // Log: Cache directory created at self::$CACHE_DIR
    }

    if (!is_writable(self::$CACHE_DIR)) {
        // Log error if logger is available
        throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
    }
}

143-147: Consider using an app-specific namespace for cache keys.

The empty namespace string ('') could lead to key collisions if multiple applications share the same cache directory. Using a namespace like 'zmsapi' would provide better isolation. Based on learnings.

Apply this diff to add a namespace:

 private static function setupCache(): void
 {
-    $psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
+    $psr6 = new FilesystemAdapter(namespace: 'zmsapi', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
     self::$cache = new Psr16Cache($psr6);
 }

155-155: Consider lazy initialization instead of eager initialization.

Calling initialize() at file load time introduces side effects during class loading. While this aligns with the existing pattern in other modules, consider lazy initialization where the cache is set up only when first accessed. This would improve testability and reduce coupling between file loading and initialization.

Example lazy initialization pattern:

public static function getCache(): CacheInterface
{
    if (self::$cache === null) {
        self::initialize();
    }
    return self::$cache;
}

Then remove the Application::initialize(); call at the end of the file and access the cache via Application::getCache() instead of Application::$cache.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9284af and 6d47fc7.

📒 Files selected for processing (1)
  • zmsapi/src/Zmsapi/Application.php (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/src/Zmsapi/Application.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsapi/src/Zmsapi/Application.php
🧬 Code graph analysis (1)
zmsapi/src/Zmsapi/Application.php (2)
zmsdb/src/Zmsdb/Application.php (5)
  • initializeCache (71-77)
  • validateCacheDirectory (79-88)
  • setupCache (90-94)
  • initialize (96-99)
  • Application (19-100)
zmscitizenapi/src/Zmscitizenapi/Application.php (5)
  • initializeCache (108-114)
  • validateCacheDirectory (146-155)
  • setupCache (157-161)
  • initialize (63-70)
  • Application (16-208)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (2)
zmsapi/src/Zmsapi/Application.php (2)

10-12: LGTM! Correct PSR-16 cache imports.

The imports align with the symfony/cache and psr/simple-cache dependencies added in composer.json.


149-152: LGTM! Clean initialization method.

The initialize method provides a clear entry point for cache setup and can be extended in the future if additional initialization steps are needed.

Comment on lines +124 to +130
private static function initializeCache(): void
{
self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
self::validateCacheDirectory();
self::setupCache();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Eliminate code duplication across Application classes.

The cache initialization logic is duplicated across zmsapi, zmsdb, and zmscitizenapi Application classes. This violates the DRY principle and increases maintenance burden. As per coding guidelines.

Consider extracting the common cache setup logic into a shared trait or base class helper:

Create a new trait:

trait CacheInitializationTrait
{
    public static ?CacheInterface $cache = null;
    public static string $CACHE_DIR;
    public static int $SOURCE_CACHE_TTL;

    private static function initializeCache(): void
    {
        self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
        self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
        if (self::$SOURCE_CACHE_TTL <= 0) {
            throw new \RuntimeException(sprintf('SOURCE_CACHE_TTL must be positive, got: %d', self::$SOURCE_CACHE_TTL));
        }
        self::validateCacheDirectory();
        self::setupCache();
    }

    private static function validateCacheDirectory(): void
    {
        if (!is_dir(self::$CACHE_DIR) && !mkdir(self::$CACHE_DIR, 0750, true)) {
            throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
        }
        if (!is_writable(self::$CACHE_DIR)) {
            throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
        }
    }

    private static function setupCache(): void
    {
        $psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
        self::$cache = new Psr16Cache($psr6);
    }
}

Then use the trait in each Application class:

class Application extends \BO\Slim\Application
{
    use CacheInitializationTrait;
    // ... rest of class
}
🤖 Prompt for AI Agents
In zmsapi/src/Zmsapi/Application.php around lines 124-130 the cache
initialization logic is duplicated across multiple Application classes; extract
the shared logic into a new reusable trait (e.g., CacheInitializationTrait) that
declares the cache properties and implements initializeCache(),
validateCacheDirectory(), and setupCache() (including the getenv fallbacks,
positive TTL validation, directory creation/writability checks, and PSR-16/PSR-6
adapter setup), then remove the duplicated methods from each Application class
and adopt the trait via "use CacheInitializationTrait" so all apps share a
single implementation.

⚠️ Potential issue | 🟠 Major

Validate SOURCE_CACHE_TTL to prevent invalid values.

The TTL cast to int could result in zero or negative values (e.g., if the environment variable is an invalid string), which may cause unexpected cache behavior.

Apply this diff to add validation:

 private static function initializeCache(): void
 {
     self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
     self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
+    if (self::$SOURCE_CACHE_TTL <= 0) {
+        throw new \RuntimeException(sprintf('SOURCE_CACHE_TTL must be positive, got: %d', self::$SOURCE_CACHE_TTL));
+    }
     self::validateCacheDirectory();
     self::setupCache();
 }
🤖 Prompt for AI Agents
In zmsapi/src/Zmsapi/Application.php around lines 124 to 130, the cast of
SOURCE_CACHE_TTL to int can yield zero or negative values for invalid env input;
change the initialization to read the env value, validate it is numeric and
greater than zero, and only then set self::$SOURCE_CACHE_TTL to that value,
otherwise fall back to the default 3600 (optionally log a warning). Ensure
validation uses strict numeric check (is_numeric) and an explicit > 0 check so
invalid strings or negative numbers don't become the TTL.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (2)
zmsdb/src/Zmsdb/Scope.php (2)

25-30: Fix cache access (null-guard + proper hit check).

Guard App::$cache and use has(); also guard set():

-        if (!$disableCache) {
-            $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }
@@
-            App::$cache->set($cacheKey, self::$cache[$cacheKey]);
+            if (App::$cache) {
+                App::$cache->set($cacheKey, self::$cache[$cacheKey]);
+            }

Also applies to: 42-42


97-103: Same cache issue in readByProviderId.

Apply the same guard/has()/set() pattern here:

-        if (!$disableCache) {
-            $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }
@@
-        App::$cache->set($cacheKey, $scopeList);
+        if (App::$cache) {
+            App::$cache->set($cacheKey, $scopeList);
+        }

Also applies to: 129-130

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d47fc7 and 330f55f.

📒 Files selected for processing (7)
  • zmsdb/src/Zmsdb/Cluster.php (5 hunks)
  • zmsdb/src/Zmsdb/Config.php (3 hunks)
  • zmsdb/src/Zmsdb/Department.php (4 hunks)
  • zmsdb/src/Zmsdb/Request.php (4 hunks)
  • zmsdb/src/Zmsdb/Scope.php (6 hunks)
  • zmsdb/src/Zmsdb/Source.php (4 hunks)
  • zmsdb/tests/Zmsdb/ScopeTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/tests/Zmsdb/ScopeTest.php
  • zmsdb/src/Zmsdb/Request.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/tests/Zmsdb/ScopeTest.php
  • zmsdb/src/Zmsdb/Request.php
🧬 Code graph analysis (7)
zmsdb/src/Zmsdb/Scope.php (6)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Config.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Department.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Source.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/src/Zmsdb/Cluster.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-49)
  • readResolvedReferences (51-62)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Scope.php (2)
  • readEntity (21-45)
  • readResolvedReferences (47-56)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-49)
  • readResolvedReferences (79-94)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/tests/Zmsdb/ScopeTest.php (1)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Request.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / module-test (zmscitizenapi, 8.3)
  • GitHub Check: call-php-unit-tests / zmsapi-test
🔇 Additional comments (8)
zmsdb/tests/Zmsdb/ScopeTest.php (1)

122-122: Good use of the new signature to bypass cache. Ensure Application::initialize() is invoked in your test setup to prevent App::$cache remaining null and causing fatal errors in other tests.

zmsdb/src/Zmsdb/Source.php (4)

5-5: LGTM!

The Application import alias is consistent with the caching pattern established across the codebase.


25-34: LGTM!

The cache implementation follows the consistent pattern used across other entities (Cluster, Department, Request, Scope). The defensive check if (App::$cache && !empty($data)) is appropriate given that App::$cache is nullable.


47-48: LGTM!

Caching the resolved entity after reference resolution is correct and matches the pattern in other entity classes.


120-120: LGTM!

Correctly bypasses the cache after writing to ensure fresh data is returned.

zmsdb/src/Zmsdb/Cluster.php (3)

5-5: LGTM!

The Application import alias is consistent with the caching pattern established across the codebase.


27-35: LGTM!

The cache implementation follows the consistent pattern used across other entities. The defensive check for App::$cache is appropriate.


47-50: LGTM!

Correctly caches the resolved cluster and returns it.

Comment on lines 16 to 22
$cacheKey = "config";
if (!$disableCache) {
$data = App::$cache->get($cacheKey);
if (App::$cache && !empty($data)) {
return $data;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix cache access: guard against null and use has() instead of empty().

  • Potential fatal: calling App::$cache->get()/delete()/set() when App::$cache is null.
  • !empty($data) drops valid cached values; use has() or a sentinel default.

Apply:

-        if (!$disableCache) {
-            $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }
@@
-        App::$cache->set($cacheKey, $config);
+        if (App::$cache) {
+            App::$cache->set($cacheKey, $config);
+        }
@@
-        App::$cache->delete('config');
+        if (App::$cache) {
+            App::$cache->delete('config');
+        }

Also applies to: 27-28, 34-36

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Config.php around lines 16-22 (and also adjust similar logic
at 27-28 and 34-36), the code calls App::$cache->get()/set()/delete() without
first checking App::$cache for null and uses !empty($data) which can reject
valid cached falsy values; change the logic to first guard with if (App::$cache)
before any get/set/delete calls, and replace the !empty($data) check with a
proper has()/exists() check on the cache (or compare get() return against a
sentinel/default) so valid cached values like 0, "0", false, or empty array are
preserved; update all three locations accordingly to avoid fatal errors and
preserve falsy cache entries.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
zmsdb/src/Zmsdb/Scope.php (3)

23-30: Fix cache miss check and add null-guard (prevents fatal on uninitialized cache).

  • Calling App::$cache->get(...) without ensuring App::$cache is set can fatally error.
  • Using !empty($data) treats valid cached values ([], 0, '', false) as misses. Prefer has() or a strict sentinel.

Apply:

-        if (!$disableCache) {
-            $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache) {
+            if (App::$cache->has($cacheKey)) {
+                return App::$cache->get($cacheKey);
+            }
+        }

Optionally run a repo-wide check to fix the same pattern elsewhere:

#!/bin/bash
# Find cache get()+empty() anti-pattern and missing null-guards
rg -n --type=php -C2 "App::\\$cache->get\\(" 
rg -n --type=php -C2 "App::\\$cache && !empty\\(\\$data\\)"

93-103: Same cache check issue in readByProviderId + guard cache write.

  • Add null-guard for App::$cache before get()/set().
  • Replace !empty() with has() (or strict sentinel).
-    public function readByProviderId($providerId, $resolveReferences = 0, $disableCache = false)
+    public function readByProviderId($providerId, $resolveReferences = 0, $disableCache = false)
     {
         $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
-
-        if (!$disableCache) {
-            $data = App::$cache->get($cacheKey);
-            if (App::$cache && !empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache) {
+            if (App::$cache->has($cacheKey)) {
+                return App::$cache->get($cacheKey);
+            }
+        }
@@
-        App::$cache->set($cacheKey, $scopeList);
+        if (App::$cache) {
+            App::$cache->set($cacheKey, $scopeList);
+        }

Also applies to: 129-131


493-494: Invalidate related cache keys on emergency update.

Clear stale cache entries before re-reading to avoid serving outdated data.

-        return $this->readEntity($scopeId, 0, true);
+        if (App::$cache) {
+            foreach ([0, 1, 2] as $level) {
+                App::$cache->delete("scope-$scopeId-$level");
+            }
+        }
+        return $this->readEntity($scopeId, 0, true);
🧹 Nitpick comments (3)
zmsdb/src/Zmsdb/Scope.php (2)

21-21: Avoid flag parameters; provide explicit no-cache variants.

Boolean $disableCache flags harm readability (Clean Code). Consider dedicated methods:

  • readEntityNoCache($scopeId, $resolveReferences = 0)
  • readByProviderIdNoCache($providerId, $resolveReferences = 0)

This keeps APIs intention‑revealing and avoids branching inside.

As per coding guidelines

Also applies to: 93-93


21-45: Consolidate dual caches to reduce drift.

Maintaining both self::$cache and App::$cache risks inconsistencies. Prefer one authoritative layer (PSR-16) and optionally add an in-request static cache with strict invalidation on writes.

As per coding guidelines

zmsdb/src/Zmsdb/Organisation.php (1)

31-31: Remove commented debug statement; use Monolog if needed.

Delete stale //error_log(...) or replace with proper logger at appropriate level.

As per coding guidelines

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 330f55f and 1ce4a6c.

📒 Files selected for processing (3)
  • zmsdb/src/Zmsdb/Organisation.php (1 hunks)
  • zmsdb/src/Zmsdb/Scope.php (7 hunks)
  • zmsdb/tests/Zmsdb/ConfigTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/tests/Zmsdb/ConfigTest.php
  • zmsdb/src/Zmsdb/Organisation.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/tests/Zmsdb/ConfigTest.php
  • zmsdb/src/Zmsdb/Organisation.php
🧬 Code graph analysis (3)
zmsdb/src/Zmsdb/Scope.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)
zmsdb/src/Zmsdb/Department.php (1)
  • readEntity (22-49)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
zmsdb/tests/Zmsdb/ConfigTest.php (2)
zmsdb/src/Zmsdb/Organisation.php (1)
  • readEntity (15-26)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Organisation.php (1)
zmsdb/src/Zmsdb/Scope.php (1)
  • readEntity (21-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (1)
zmsdb/tests/Zmsdb/ConfigTest.php (1)

27-27: LGTM: test updated to use cache-bypass flag.

Change aligns with Config::readEntity($disableCache = false).

Comment on lines +183 to 184
return $this->readEntity($organisationId, 1, true);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Too many arguments to readEntity; will emit warning or fatal (PHP ≥8).

readEntity accepts 2 params; this call passes 3. Fix now.

Minimal fix:

-        return $this->readEntity($organisationId, 1, true);
+        return $this->readEntity($organisationId, 1);

Alternatively, align Organisation with new caching API by extending its signature and adding cache handling like Scope::readEntity.

Please confirm PHP runtime version; on PHP 8+, “Too many arguments” can be fatal depending on setup.


🏁 Script executed:

#!/bin/bash
rg -nP "function\s+readEntity\s*\(" -g "*.php"

Length of output: 4017


🏁 Script executed:

#!/bin/bash
rg -n "error_log" -g "zmsdb/src/Zmsdb/Organisation.php"

Length of output: 101


Remove invalid third argument and clean up debug code

  • zmsdb/src/Zmsdb/Organisation.php line 183: readEntity signature is ($itemId, $resolveReferences = 0)—remove the third true argument:
-        return $this->readEntity($organisationId, 1, true);
+        return $this->readEntity($organisationId, 1);
  • zmsdb/src/Zmsdb/Organisation.php line 31: remove the commented //error_log("Organisation Level $resolveReferences"); or replace with proper Monolog logging.
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Organisation.php around lines 183-184 and line 31, the call
return $this->readEntity($organisationId, 1, true); uses an invalid third
argument—remove the third parameter so it becomes readEntity($organisationId,
1); and also delete the commented debug line on line 31
(//error_log("Organisation Level $resolveReferences");) or replace it with a
proper Monolog logger call if runtime logging is required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php (1)

77-77: Replace error_log() with Monolog logging.

The use of error_log() violates the coding guidelines. Replace it with proper Monolog logging.

Apply this diff:

         if ($archivedProcesses instanceof ProcessListCollection) {
             $allArchivedProcesses = $archivedProcesses;
         } else {
-            error_log("Expected ProcessListCollection, received " . gettype($archivedProcesses));
+            \App::$log->error("Archived processes type mismatch", [
+                'expected' => ProcessListCollection::class,
+                'received' => gettype($archivedProcesses),
+                'scopeIds' => $scopeIds,
+                'dates' => array_map(fn($d) => $d->format('Y-m-d'), $dates)
+            ]);
         }

Note: Ensure \App::$log is properly initialized in your Application class, as per coding guidelines.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56d64cd and dc730a7.

📒 Files selected for processing (1)
  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / zmsapi-test


$query = new Query();
$cluster = $query->readEntity($args['id'], 0);
$cluster = $query->readEntity($args['id'], 0, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the cache bypass decision.

The third parameter 1 disables caching for this cluster read. Consider the following:

  • Is bypassing the cache necessary here? Cluster data is typically stable and would benefit from caching.
  • This creates inconsistency: the readQueueList call at line 54 doesn't disable caching.
  • If cache bypass is required (e.g., to ensure fresh cluster configuration), add a comment explaining why.

Run the following script to check how cache bypass is used across similar read operations:


🏁 Script executed:

#!/bin/bash
# Description: Check cache bypass patterns in readEntity calls across the codebase

# Search for readEntity calls with the cache bypass parameter
rg -nP --type=php -C3 '->readEntity\([^)]+,\s*\d+,\s*1\)'

Length of output: 404


🏁 Script executed:

#!/bin/bash
# Description: Find all readEntity calls disabling cache (third parameter = 1)
rg -n --type=php -C3 -e '->readEntity\([^,]+,\s*[^,]+,\s*1\s*\)'

Length of output: 6905


🏁 Script executed:

#!/bin/bash
# Display the top of ProcessListByClusterAndDate to inspect imports and Query reference
sed -n '1,60p' zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php

Length of output: 1892


Replace error_log() with Monolog logging.
The error_log() call (around line 77) must be removed and replaced with the application’s Monolog logger (e.g. $this->logger->error('Message', ['error'=>$e->getMessage()]);).

🤖 Prompt for AI Agents
In zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php around line 47, remove the
error_log() call and replace it with the application's Monolog logger;
specifically, call $this->logger->error with a descriptive message and include
the exception message in the context array (e.g. ['error' => $e->getMessage()]);
ensure you reference the existing $this->logger instance and keep the same
exception variable ($e) used in the catch block.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php (2)

77-77: Replace error_log() with Monolog logging.

The error_log() call violates the project's logging guidelines. Replace it with the application's Monolog logger.

As per coding guidelines, apply this diff:

-            error_log("Expected ProcessListCollection, received " . gettype($archivedProcesses));
+            $this->logger->error("Expected ProcessListCollection", [
+                'receivedType' => gettype($archivedProcesses),
+                'scopeIds' => $scopeIds,
+                'dates' => array_map(fn($d) => $d->format('Y-m-d'), $dates)
+            ]);

47-47: Reconsider the cache bypass decision.

The third parameter true disables caching for cluster reads, which seems counterproductive in a PR implementing caching. This also creates inconsistency: the readQueueList call at line 54 uses caching, while this cluster read bypasses it. Cluster data is typically stable and would benefit from caching.

If cache bypass is required for this specific use case, add a comment explaining the rationale.

🧹 Nitpick comments (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)

6-6: Remove unused import.

The BO\Zmsclient\Http import is not referenced anywhere in this test file.

Apply this diff:

-use BO\Zmsclient\Http;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc730a7 and 5418fb7.

📒 Files selected for processing (2)
  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php (1 hunks)
  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
  • zmsapi/src/Zmsapi/ProcessListByClusterAndDate.php
🧬 Code graph analysis (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)
zmsclient/src/Zmsclient/Http.php (1)
  • Http (16-260)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)

13-19: Cache mock setup looks good.

The setUp() method correctly initializes a cache mock and assigns it to \App::$cache, enabling the tests to work with the new caching infrastructure.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)

6-6: Remove unused import.

The BO\Zmsclient\Http import is not used anywhere in this test file.

Apply this diff to remove the unused import:

-use BO\Zmsclient\Http;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5418fb7 and 0e11f3d.

📒 Files selected for processing (1)
  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
🧬 Code graph analysis (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)
zmsclient/src/Zmsclient/Http.php (1)
  • Http (16-260)
🪛 GitHub Actions: Combined Workflow
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php

[error] 17-17: PHPUnit error: Creation of dynamic property BO\Zmsapi\Tests\ProcessListByClusterAndDateTest::$cacheMock is deprecated.


[error] 17-17: PHPUnit error: Creation of dynamic property BO\Zmsapi\Tests\ProcessListByClusterAndDateTest::$cacheMock is deprecated.

Comment on lines 13 to 19
public function setUp(): void
{
parent::setUp();

$this->cacheMock = $this->createMock(CacheInterface::class);
\App::$cache = $this->cacheMock;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Declare $cacheMock as a class property to fix deprecation warning.

The pipeline failure indicates that creating $this->cacheMock dynamically is deprecated in PHP 8.2+. This will become a fatal error in PHP 9.0.

Add the property declaration to the class:

 class ProcessListByClusterAndDateTest extends Base
 {
     protected $classname = "ProcessListByClusterAndDate";
+
+    protected $cacheMock;
 
     public function setUp(): void

Additionally, consider whether the cache mock needs expectations configured. Currently, it's created but not configured to return any values or verify interactions. If the code under test uses the cache, you may want to add expectations like:

$this->cacheMock->expects($this->any())
    ->method('get')
    ->willReturn(null);

🏁 Script executed:

#!/bin/bash
set -e
echo "Class declaration context:"
rg -n 'class ProcessListByClusterAndDateTest' -C3 zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
echo
echo "Occurrences of cacheMock:"
rg -n 'cacheMock' -C1 zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php

Length of output: 371


Declare cacheMock as a class property
Add the property to avoid PHP 8.2’s dynamic‐property deprecation:

 class ProcessListByClusterAndDateTest extends Base
 {
     protected $classname = "ProcessListByClusterAndDate";
+    protected CacheInterface $cacheMock;

(Optional) configure expectations on this mock if the code under test invokes the cache.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function setUp(): void
{
parent::setUp();
$this->cacheMock = $this->createMock(CacheInterface::class);
\App::$cache = $this->cacheMock;
}
class ProcessListByClusterAndDateTest extends Base
{
protected $classname = "ProcessListByClusterAndDate";
protected CacheInterface $cacheMock;
public function setUp(): void
{
parent::setUp();
$this->cacheMock = $this->createMock(CacheInterface::class);
\App::$cache = $this->cacheMock;
}
// ...
}
🧰 Tools
🪛 GitHub Actions: Combined Workflow

[error] 17-17: PHPUnit error: Creation of dynamic property BO\Zmsapi\Tests\ProcessListByClusterAndDateTest::$cacheMock is deprecated.


[error] 17-17: PHPUnit error: Creation of dynamic property BO\Zmsapi\Tests\ProcessListByClusterAndDateTest::$cacheMock is deprecated.

🤖 Prompt for AI Agents
In zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php around lines 13 to
19, the test assigns $this->cacheMock dynamically which triggers PHP 8.2 dynamic
property deprecation; declare a class property (e.g. private or protected
CacheInterface $cacheMock;) at the top of the test class to make the mock a
typed property, and then keep the setUp assignment; optionally, configure
expectations on this mock in setUp or individual tests if the code under test
calls the cache.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1)

6-6: Remove unused import.

The BO\Zmsclient\Http import is not used anywhere in this test file.

Apply this diff to remove the unused import:

-use BO\Zmsclient\Http;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5418fb7 and 943fcb2.

📒 Files selected for processing (1)
  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / zmsapi-test

Comment on lines 14 to 20
public function setUp(): void
{
parent::setUp();

$this->cacheMock = $this->createMock(CacheInterface::class);
\App::$cache = $this->cacheMock;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add tearDown to ensure test isolation.

The cache mock is assigned to the global static property \App::$cache but there's no tearDown() method to clean up this state after each test. This can cause test pollution if other tests in the suite also interact with \App::$cache.

Add a tearDown() method to reset the static state:

+    public function tearDown(): void
+    {
+        \App::$cache = null;
+        parent::tearDown();
+    }
🤖 Prompt for AI Agents
In zmsapi/tests/Zmsapi/ProcessListByClusterAndDateTest.php around lines 14 to
20, the setUp assigns a cache mock to the global static \App::$cache but no
tearDown is provided to reset that static, risking test pollution; add a public
function tearDown(): void that sets \App::$cache = null (or restores previous
value if you saved one) and calls parent::tearDown() to ensure the mock is
cleared after each test.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsdb/src/Zmsdb/DayOff.php (1)

68-96: Guard App::$cache usage and ensure cache initialization

  • Wrap all App::$cache->get(…) and App::$cache->set(…) calls in a null check (e.g. if (App::$cache) { … }) in DayOff.php (lines 75, 94) and likewise in other DB classes.
  • Invoke Application::initialize() in every entry point (index.php, bootstrap.php, console.php) so App::$cache is never null at runtime.
♻️ Duplicate comments (7)
zmsdb/src/Zmsdb/Scope.php (4)

23-30: This comment duplicates a previous review.

The cache check logic issues identified in the previous review remain unaddressed.


42-42: This comment duplicates a previous review.

The missing null check before cache write remains unaddressed.


103-108: This comment duplicates a previous review.

The cache check logic issues identified in the previous review remain unaddressed.


503-503: This comment duplicates a previous review.

The cache invalidation concern identified in the previous review remains unaddressed.

zmsdb/src/Zmsdb/Cluster.php (2)

370-370: Use boolean true instead of integer 1 for type consistency.

The third parameter $disableCache expects a boolean but receives an integer 1. While PHP will coerce this to true, it violates type expectations and clean code principles.

Apply this diff:

-        return $this->readEntity($lastInsertId, 1, 1);
+        return $this->readEntity($lastInsertId, 1, true);

391-391: Use boolean true instead of integer 1 for type consistency.

The third parameter $disableCache expects a boolean but receives an integer 1. While PHP will coerce this to true, it violates type expectations and clean code principles.

Apply this diff:

-        return $this->readEntity($clusterId, 1, 1);
+        return $this->readEntity($clusterId, 1, true);
zmsdb/src/Zmsdb/DayOff.php (1)

94-94: Fix cache null-check before set.

Calling App::$cache->set() will fail if App::$cache is null. This critical issue was flagged in the previous review but remains unfixed. Add a null check before attempting to cache the data.

Apply this diff to fix the issue:

-
-        App::$cache->set($cacheKey, $dayOffList);
-
+        
+        if (App::$cache) {
+            App::$cache->set($cacheKey, $dayOffList);
+        }
+        
         return $dayOffListCommon->addList($dayOffList);
🧹 Nitpick comments (5)
zmsdb/src/Zmsdb/Closure.php (1)

12-12: Add type hints for parameters.

The method parameters lack type declarations, which reduces type safety and IDE support.

Apply this diff to add type hints:

-    public function readByScopeId($scopeId = 0, $disableCache = false)
+    public function readByScopeId(int $scopeId = 0, bool $disableCache = false)
zmsdb/src/Zmsdb/Scope.php (4)

47-56: Add type hint for the $disableCache parameter.

The method signature is missing a type hint for the $disableCache parameter, which reduces type safety and clarity.

Apply this diff to add the type hint:

     public function readResolvedReferences(
         \BO\Zmsentities\Schema\Entity $scope,
         $resolveReferences,
-        $disableCache = false
+        bool $disableCache = false
     ) {

59-96: Add type hint for the $disableCache parameter.

Similar to readResolvedReferences, this method is missing a type hint for the $disableCache parameter.

Apply this diff:

     public function readByClusterId(
         $clusterId,
         $resolveReferences = 0,
-        $disableCache = false
+        bool $disableCache = false
     ) {

99-99: Add type hint for the $disableCache parameter.

This method also needs a type hint for the $disableCache parameter to maintain consistency.

Apply this diff:

-    public function readByProviderId($providerId, $resolveReferences = 0, $disableCache = false)
+    public function readByProviderId($providerId, $resolveReferences = 0, bool $disableCache = false)

99-142: Consider unifying the caching strategy.

readEntity() uses both a static local cache (self::$cache) and the application-level cache (App::$cache), while readByProviderId() only uses App::$cache. This inconsistency may lead to different performance characteristics and cache invalidation behavior.

Consider either:

  1. Adding local cache to readByProviderId() for consistency with readEntity(), or
  2. Documenting why different strategies are used for these methods.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 943fcb2 and 126eb2c.

📒 Files selected for processing (4)
  • zmsdb/src/Zmsdb/Closure.php (2 hunks)
  • zmsdb/src/Zmsdb/Cluster.php (5 hunks)
  • zmsdb/src/Zmsdb/DayOff.php (4 hunks)
  • zmsdb/src/Zmsdb/Scope.php (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Scope.php
🧬 Code graph analysis (4)
zmsdb/src/Zmsdb/Cluster.php (2)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Scope.php (4)
  • readEntity (21-45)
  • readResolvedReferences (47-57)
  • Scope (17-608)
  • readByClusterId (59-97)
zmsdb/src/Zmsdb/Closure.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Query/Closure.php (1)
  • Closure (8-107)
zmsdb/src/Zmsdb/DayOff.php (1)
  • readByScopeId (68-97)
zmsdb/src/Zmsdb/DayOff.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Closure.php (1)
  • readByScopeId (12-39)
zmsentities/src/Zmsentities/Collection/Base.php (1)
  • addList (145-151)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (3)
  • readResolvedReferences (53-60)
  • readByScopeId (98-111)
  • readEntity (27-51)
zmsdb/src/Zmsdb/Source.php (2)
  • readResolvedReferences (79-94)
  • readEntity (25-49)
zmsdb/src/Zmsdb/Department.php (3)
  • readResolvedReferences (51-62)
  • readByScopeId (82-91)
  • readEntity (22-49)
zmsdb/src/Zmsdb/DayOff.php (2)
  • DayOff (9-206)
  • readByScopeId (68-97)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-107)
  • readEntity (99-106)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-42)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsmessaging, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsadmin, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsstatistic, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsticketprinter, 8.3)
  • GitHub Check: call-php-code-quality / module-code-quality (zmsdldb, 8.3)
  • GitHub Check: call-php-unit-tests / zmsapi-test
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: build (zmscitizenview)
🔇 Additional comments (9)
zmsdb/src/Zmsdb/Closure.php (2)

5-5: LGTM!

The import alias follows the established pattern across other files in the codebase.


14-21: LGTM!

The cache read logic properly guards against null cache and respects the $disableCache flag. The early return pattern is clean and efficient.

zmsdb/src/Zmsdb/Scope.php (2)

7-7: LGTM!

The import statement correctly adds the Application class alias needed for the caching functionality.


449-449: LGTM!

Correctly passes true for $disableCache to ensure fresh data is returned after the update operation.

zmsdb/src/Zmsdb/Cluster.php (3)

5-5: LGTM!

The import alias enables access to the static cache instance. This follows the pattern established in other files implementing the caching layer.


27-35: LGTM!

The cache read logic correctly implements the read-through pattern with proper guards:

  • Checks both $disableCache and App::$cache before accessing cache
  • Returns early on cache hit
  • Cache key format matches the pattern used in other entities

53-60: LGTM!

The signature update correctly adds the $disableCache parameter and threads it through to the nested scope read. The default value of false maintains backward compatibility with existing callers.

zmsdb/src/Zmsdb/DayOff.php (2)

5-5: LGTM!

The import alias enables clean access to the application-wide cache and follows the pattern used in related files.


68-79: Cache null-check order fixed correctly.

The previous critical issue regarding cache access order has been resolved. The code now properly checks !$disableCache && App::$cache before calling get(), preventing potential null pointer errors. The addition of the $disableCache parameter provides a useful bypass mechanism.

}
}

App::$cache->set($cacheKey, $closureList);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Unconditional cache write violates safety and parameter contract.

The cache write operation lacks essential guards:

  1. Null pointer risk: If App::$cache is null (e.g., initialization skipped in tests), this line throws a fatal error.
  2. Contract violation: Writing to cache when $disableCache is true violates the explicit intent of the parameter.

The cache read at line 16 properly checks !$disableCache && App::$cache, but the write omits these guards entirely.

Apply this diff to add proper guards:

-        App::$cache->set($cacheKey, $closureList);
+        if (!$disableCache && App::$cache) {
+            App::$cache->set($cacheKey, $closureList);
+        }
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Closure.php around line 36, the unconditional cache write
violates safety and the $disableCache contract; update the code to only call
App::$cache->set($cacheKey, $closureList) when !$disableCache and App::$cache is
truthy (i.e., mirror the guard used on cache read at line 16) so you avoid null
pointer errors and respect the disable flag.

Comment on lines 47 to 50
$cluster = $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
App::$cache->set($cacheKey, $cluster);

return $cluster;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null check before cache write to prevent fatal error.

Line 48 calls App::$cache->set() without verifying that App::$cache is not null, which could cause a fatal error if cache initialization fails. Additionally, the cache write doesn't respect the $disableCache flag, creating inconsistent behavior where cache reads are skipped but writes still occur.

Apply this diff to add proper guards:

 
         $cluster = $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
-        App::$cache->set($cacheKey, $cluster);
+        if (!$disableCache && App::$cache) {
+            App::$cache->set($cacheKey, $cluster);
+        }
 
         return $cluster;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$cluster = $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
App::$cache->set($cacheKey, $cluster);
return $cluster;
$cluster = $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
if (!$disableCache && App::$cache) {
App::$cache->set($cacheKey, $cluster);
}
return $cluster;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsdb/src/Zmsdb/Provider.php (1)

115-122: Fix import path: removeCache() receives wrong type and is never using writeImportEntity’s return

This causes the pipeline TypeError and fails to invalidate correct keys.

Apply:

-        foreach ($providerList as $provider) {
-            $this->writeImportEntity($provider, $source);
-            $this->removeCache($provider);
-        }
+        foreach ($providerList as $providerData) {
+            $providerEntity = $this->writeImportEntity($providerData, $source);
+            if ($providerEntity instanceof Entity) {
+                $this->removeCache($providerEntity);
+            } else {
+                // Fallback: evict by identifiers when entity not returned
+                $this->removeCacheById($source, (int) ($providerData['id'] ?? 0));
+            }
+        }
♻️ Duplicate comments (5)
zmsdb/src/Zmsdb/Closure.php (1)

36-37: Guard cache write and honor disableCache

Set is unconditional; can NPE when cache is null and ignores disableCache flag.

-        App::$cache->set($cacheKey, $closureList);
+        if (!$disableCache && App::$cache) {
+            App::$cache->set($cacheKey, $closureList);
+        }
zmsdb/src/Zmsdb/Cluster.php (2)

438-455: Extract cache invalidation logic to reduce duplication.

This method duplicates the cache invalidation pattern seen in Source.php, Request.php, Department.php, and Scope.php. Consider extracting to a shared helper to follow DRY principles. Based on coding guidelines.


48-50: Guard cache write with $disableCache check.

The cache write occurs unconditionally, even when $disableCache is true. This creates inconsistent behavior where cache reads are skipped but writes still happen. Based on past review comments.

Apply this diff:

-        if (App::$cache) {
-            App::$cache->set($cacheKey, $cluster);
-        }
+        if (!$disableCache && App::$cache) {
+            App::$cache->set($cacheKey, $cluster);
+        }
zmsdb/src/Zmsdb/Request.php (1)

231-263: Extract cache invalidation logic to reduce duplication.

This method shares the same cache invalidation pattern with Source.php, Cluster.php, Department.php, and Scope.php. Consider extracting to a shared helper to follow DRY principles. Based on coding guidelines.

zmsdb/src/Zmsdb/Scope.php (1)

655-684: Extract cache invalidation logic to reduce duplication.

This method shares the same cache invalidation pattern with Source.php, Cluster.php, Request.php, and Department.php. Consider extracting to a shared helper to follow DRY principles. Based on coding guidelines.

🧹 Nitpick comments (10)
zmsdb/src/Zmsdb/Department.php (3)

24-41: Avoid caching misses; respect disableCache on write

Only cache successful reads. Also mirror the read guard on writes.

Apply:

-        if (empty($department)) {
+        if (empty($department)) {
             $query = new Query\Department(Query\Base::SELECT);
             $query->addEntityMapping()
                 ->addResolvedReferences($resolveReferences)
                 ->addConditionDepartmentId($departmentId);
             $department = $this->fetchOne($query, new Entity());
-
-            if (App::$cache) {
-                App::$cache->set($cacheKey, $department);
-            }
+            if (!$disableCache && App::$cache && isset($department['id']) && $department['id']) {
+                App::$cache->set($cacheKey, $department);
+            }
         }

185-187: Creation path: removeCache uses unsaved entity id

On insert, $entity usually has id=0; invalidation is ineffective.

Either drop this invalidation or use the new id:

-        $this->removeCache($entity);
+        // Optional: no invalidation needed for create unless list caches exist
+        // If you keep it, use the persisted id:
+        // $persisted = $this->readEntity($lastInsertId, 0, true);
+        // $this->removeCache($persisted);

413-430: Prefer id-based cache invalidation API

removeCache($department) couples call sites to entity shape and fails on null. Use an id-based variant.

Example:

-    public function removeCache($department)
+    public function removeCacheById(int $departmentId): void
     {
         if (!App::$cache) {
             return;
         }
-
-        if (App::$cache->has("department-$department->id-0")) {
-            App::$cache->delete("department-$department->id-0");
+        foreach ([0,1,2] as $lvl) {
+            $key = "department-$departmentId-$lvl";
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
         }
-
-        if (App::$cache->has("department-$department->id-1")) {
-            App::$cache->delete("department-$department->id-1");
-        }
-
-        if (App::$cache->has("department-$department->id-2")) {
-            App::$cache->delete("department-$department->id-2");
-        }
     }

Then call removeCacheById($departmentId) where appropriate.

zmsdb/src/Zmsdb/DayOff.php (4)

52-57: Use has() for cache existence check

Prefer has() over get()+empty() for clarity and to avoid edge cases.

-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }

83-90: Propagate disableCache and use has() for scope cache check

  • Pass $disableCache into readCommon() for consistent bypass.
  • Prefer has() for existence check.
-        $dayOffListCommon = $this->readCommon();
+        $dayOffListCommon = $this->readCommon($disableCache);
@@
-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $dayOffListCommon->addList($data);
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return $dayOffListCommon->addList(App::$cache->get($cacheKey));
+        }

105-109: Respect disableCache when writing cache

Align set() with the bypass flag.

-        if (App::$cache) {
+        if (!$disableCache && App::$cache) {
             App::$cache->set($cacheKey, $dayOffList);
         }

227-236: Add per-scope cache invalidation helper

Add a helper to evict "dayOffsByScope-$scopeId" and call it from write/delete operations that affect scope-specific dayoffs.

Example:

     public function removeCache()
     {
         if (!App::$cache) {
             return;
         }
 
         if (App::$cache->has("dayOffs")) {
             App::$cache->delete("dayOffs");
         }
     }
+
+    public function removeCacheByScope(int $scopeId): void
+    {
+        if (!App::$cache) {
+            return;
+        }
+        $key = "dayOffsByScope-$scopeId";
+        if (App::$cache->has($key)) {
+            App::$cache->delete($key);
+        }
+    }

Then call removeCacheByScope() from callers that modify scope-related dayoffs (e.g., after writeDepartmentDayoffs).

zmsdb/src/Zmsdb/Closure.php (1)

16-21: Use has() for cache existence

Consistency with other modules and avoids false negatives.

-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }
zmsdb/src/Zmsdb/Source.php (1)

172-189: Extract cache invalidation logic to reduce duplication.

The removeCache method hardcodes resolve reference levels (0, 1, 2) and repeats this pattern across multiple files (Cluster, Request, Scope, Department). Consider extracting this to a shared helper method to follow DRY principles and make future changes easier. Based on coding guidelines.

Example refactor:

// In Application.php or a new CacheHelper class
public static function invalidateEntityCache(string $keyPrefix, $entityId, array $levels = [0, 1, 2]): void
{
    if (!self::$cache) {
        return;
    }
    
    foreach ($levels as $level) {
        $key = "$keyPrefix-$entityId-$level";
        if (self::$cache->has($key)) {
            self::$cache->delete($key);
        }
    }
}

Then use it:

public function removeCache($sourceName)
{
    App::invalidateEntityCache('source', $sourceName);
}
zmsdb/src/Zmsdb/Request.php (1)

155-175: Bypass cache when reading after write for consistency.

The method calls removeCache() but then returns readEntity() without passing disableCache=true. This means the read might still fetch stale cached data if another process cached it between the invalidation and read. For consistency with writeImportEntity (line 201) and similar patterns in other files, consider bypassing the cache. Based on coding guidelines.

Apply this diff:

-        return $this->readEntity($entity->getSource(), $entity->getId());
+        return $this->readEntity($entity->getSource(), $entity->getId(), 0, true);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8e31fb and e163cb8.

📒 Files selected for processing (10)
  • zmsapi/src/Zmsapi/AvailabilityClosureToggle.php (1 hunks)
  • zmsdb/src/Zmsdb/Closure.php (4 hunks)
  • zmsdb/src/Zmsdb/Cluster.php (9 hunks)
  • zmsdb/src/Zmsdb/Config.php (5 hunks)
  • zmsdb/src/Zmsdb/DayOff.php (5 hunks)
  • zmsdb/src/Zmsdb/Department.php (6 hunks)
  • zmsdb/src/Zmsdb/Provider.php (9 hunks)
  • zmsdb/src/Zmsdb/Request.php (8 hunks)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
  • zmsdb/src/Zmsdb/Source.php (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsapi/src/Zmsapi/AvailabilityClosureToggle.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Source.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Config.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsapi/src/Zmsapi/AvailabilityClosureToggle.php
🧬 Code graph analysis (10)
zmsdb/src/Zmsdb/Source.php (5)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (4)
  • readEntity (27-53)
  • readResolvedReferences (55-63)
  • readList (82-99)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Provider.php (4)
  • readEntity (11-34)
  • Provider (9-197)
  • readListBySource (51-67)
  • removeCache (176-196)
zmsdb/src/Zmsdb/Request.php (4)
  • readEntity (15-41)
  • readListBySource (111-132)
  • Request (13-264)
  • removeCache (231-263)
zmsdb/src/Zmsdb/Scope.php (6)
  • readEntity (21-45)
  • readResolvedReferences (47-57)
  • readListBySource (212-221)
  • Scope (17-685)
  • readList (241-270)
  • removeCache (655-684)
zmsdb/src/Zmsdb/DayOff.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Collection/Base.php (2)
  • addEntity (109-113)
  • addList (145-151)
zmsdb/src/Zmsdb/Closure.php (1)
  • readByScopeId (12-39)
zmsdb/src/Zmsdb/Provider.php (6)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Provider.php (2)
  • Provider (5-104)
  • getSource (64-67)
zmsdb/src/Zmsdb/Query/Provider.php (4)
  • Provider (5-86)
  • addConditionRequestCsv (63-77)
  • addConditionProviderId (48-52)
  • addConditionProviderSource (54-58)
zmsentities/src/Zmsentities/Collection/ProviderList.php (1)
  • ProviderList (5-94)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Request.php (2)
  • readEntity (15-41)
  • removeCache (231-263)
zmsdb/src/Zmsdb/Closure.php (4)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Query/Closure.php (2)
  • Closure (8-107)
  • addConditionId (94-98)
zmsentities/src/Zmsentities/Closure.php (1)
  • Closure (5-50)
zmsdb/src/Zmsdb/DayOff.php (1)
  • readByScopeId (79-110)
zmsdb/src/Zmsdb/Config.php (5)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • updateEntity (391-405)
zmsdb/src/Zmsdb/Provider.php (1)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Scope.php (2)
  • readEntity (21-45)
  • updateEntity (470-483)
zmsdb/src/Zmsdb/Source.php (1)
  • readEntity (25-50)
zmsdb/src/Zmsdb/Request.php (5)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-48)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Provider.php (3)
  • readEntity (11-34)
  • testSource (169-174)
  • removeCache (176-196)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-50)
  • removeCache (172-189)
zmsdb/src/Zmsdb/Scope.php (7)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (5)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • readList (82-99)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Source.php (4)
  • readResolvedReferences (80-101)
  • readList (57-73)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (176-196)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (231-263)
  • readEntity (15-41)
zmsdb/src/Zmsdb/Department.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (3)
  • readResolvedReferences (55-63)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Scope.php (3)
  • readResolvedReferences (47-57)
  • removeCache (655-684)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Cluster.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Department.php (3)
  • readEntity (22-48)
  • readResolvedReferences (50-61)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Scope.php (5)
  • readEntity (21-45)
  • readResolvedReferences (47-57)
  • Scope (17-685)
  • readByClusterId (59-113)
  • removeCache (655-684)
zmsapi/src/Zmsapi/AvailabilityClosureToggle.php (1)
zmsdb/src/Zmsdb/Closure.php (2)
  • Closure (10-117)
  • deleteEntity (75-85)
🪛 GitHub Actions: Combined Workflow
zmsdb/src/Zmsdb/Provider.php

[error] 176-176: Uncaught TypeError: BO\Zmsdb\Provider::removeCache(): Argument #1 ($provider) must be of type BO\Zmsentities\Provider, BO\Zmsdldb\Entity\Location given.

zmsdb/src/Zmsdb/Scope.php

[error] 241-241: PHPCS: Arguments with default values must be at the end of the argument list.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
zmsapi/src/Zmsapi/AvailabilityClosureToggle.php (1)

53-55: Call signature aligned with new API — OK

Passing the entity to deleteEntity() matches the updated zmsdb Closure::deleteEntity signature.

zmsdb/src/Zmsdb/Config.php (3)

14-27: Config caching path looks good

Guards with has() and null-checks; set() behind App::$cache guard. LGTM.


59-64: Cache invalidation on update — OK

Clearing "config" after update and re-reading bypassing cache is correct.


77-79: Property mutation invalidation — OK

Deleting "config" cache before replace/delete keeps reads consistent.

Also applies to: 98-102

zmsdb/src/Zmsdb/Provider.php (1)

11-17: readEntity cache path — OK

has()+get guarded by disableCache. LGTM.

Comment on lines 217 to 225
public function deleteEntity($itemId)
{
$query = new Query\DayOff(Query\Base::DELETE);
$query->addConditionDayOffId($itemId);

$this->removeCache();

return ($this->deleteItem($query));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing invalidation for per-scope caches leads to stale reads

deleteEntity() only clears the global "dayOffs" cache. Keys like "dayOffsByScope-$scopeId" remain stale.

Consider one of:

  • Look up the entity’s owning scope/department and also delete "dayOffsByScope-$scopeId".
  • Add removeCacheByScope(int $scopeId) and call it from write paths that know the scope(s).

Comment on lines 176 to 196
public function removeCache(Entity $provider)
{
if (!App::$cache) {
return;
}

$source = $provider->getSource();
$providerId = $provider->getId();

if (App::$cache->has("request-$source-$providerId-0")) {
App::$cache->delete("request-$source-$providerId-0");
}

if (App::$cache->has("request-$source-$providerId-1")) {
App::$cache->delete("request-$source-$providerId-1");
}

if (App::$cache->has("request-$source-$providerId-2")) {
App::$cache->delete("request-$source-$providerId-2");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

removeCache() deletes wrong keys and has an overly strict parameter type

  • Wrong keys: it deletes request-* instead of provider-*.
  • Typehint causes TypeError in import path.

Apply:

-    public function removeCache(Entity $provider)
+    public function removeCache($provider): void
     {
         if (!App::$cache) {
             return;
         }
-
-        $source = $provider->getSource();
-        $providerId = $provider->getId();
-
-        if (App::$cache->has("request-$source-$providerId-0")) {
-            App::$cache->delete("request-$source-$providerId-0");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-1")) {
-            App::$cache->delete("request-$source-$providerId-1");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-2")) {
-            App::$cache->delete("request-$source-$providerId-2");
-        }
+        // Resolve identifiers
+        if ($provider instanceof Entity) {
+            $source = $provider->getSource();
+            $providerId = $provider->getId();
+        } elseif (is_array($provider)) {
+            $source = $provider['source'] ?? 'dldb';
+            $providerId = (int) ($provider['id'] ?? 0);
+        } else {
+            return;
+        }
+        foreach ([0,1,2] as $lvl) {
+            $key = "provider-$source-$providerId-$lvl";
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
     }

Optionally, expose a dedicated helper to invalidate by identifiers:

+    public function removeCacheById(string $source, int $providerId): void
+    {
+        if (!App::$cache) return;
+        foreach ([0,1,2] as $lvl) {
+            $key = "provider-$source-$providerId-$lvl";
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function removeCache(Entity $provider)
{
if (!App::$cache) {
return;
}
$source = $provider->getSource();
$providerId = $provider->getId();
if (App::$cache->has("request-$source-$providerId-0")) {
App::$cache->delete("request-$source-$providerId-0");
}
if (App::$cache->has("request-$source-$providerId-1")) {
App::$cache->delete("request-$source-$providerId-1");
}
if (App::$cache->has("request-$source-$providerId-2")) {
App::$cache->delete("request-$source-$providerId-2");
}
}
public function removeCache($provider): void
{
if (!App::$cache) {
return;
}
// Resolve identifiers
if ($provider instanceof Entity) {
$source = $provider->getSource();
$providerId = $provider->getId();
} elseif (is_array($provider)) {
$source = $provider['source'] ?? 'dldb';
$providerId = (int) ($provider['id'] ?? 0);
} else {
return;
}
foreach ([0, 1, 2] as $lvl) {
$key = "provider-$source-$providerId-$lvl";
if (App::$cache->has($key)) {
App::$cache->delete($key);
}
}
}
public function removeCacheById(string $source, int $providerId): void
{
if (!App::$cache) {
return;
}
foreach ([0, 1, 2] as $lvl) {
$key = "provider-$source-$providerId-$lvl";
if (App::$cache->has($key)) {
App::$cache->delete($key);
}
}
}
🧰 Tools
🪛 GitHub Actions: Combined Workflow

[error] 176-176: Uncaught TypeError: BO\Zmsdb\Provider::removeCache(): Argument #1 ($provider) must be of type BO\Zmsentities\Provider, BO\Zmsdldb\Entity\Location given.

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Provider.php around lines 176-196, remove the strict Entity
typehint on removeCache and change the deleted cache keys from "request-..." to
"provider-..."; update the method to accept a generic $provider (or mixed),
derive $source and $providerId safely (e.g., if $provider is an object call
getSource()/getId(), otherwise treat as identifier), and call
App::$cache->delete for "provider-$source-$providerId-0", "-1", and "-2" only
when those keys exist; optionally extract this logic into a small helper
invalidateProviderCacheByIdentifiers($source, $providerId) and call it from
removeCache.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsdb/src/Zmsdb/Provider.php (1)

51-64: Rename readListBySource’s $providerIdCsv to $requestIdCsv
Method currently uses the fourth parameter to filter by request IDs. Update the signature, conditional, addConditionRequestCsv call, and all invocations.

- public function readListBySource($source, $resolveReferences = 0, $isAssigned = null, $providerIdCsv = null)
+ public function readListBySource($source, $resolveReferences = 0, $isAssigned = null, $requestIdCsv = null)
 {
     …
-    if (null !== $providerIdCsv) {
-        $query->addConditionRequestCsv($providerIdCsv, $source);
+    if (null !== $requestIdCsv) {
+        $query->addConditionRequestCsv($requestIdCsv, $source);
     }

Run rg -nP '\breadListBySource\s*\(' to update tests, API wrappers, and helper code.

♻️ Duplicate comments (2)
zmsdb/src/Zmsdb/Provider.php (2)

123-149: Fix import mapping (country/region) and return a Provider entity (not mixed array/entity)

  • contact__country and contact__region are set from city → data corruption.
  • Method returns array or Entity depending on branch → brittle API.
     public function writeImportEntity($provider, $source = 'dldb')
     {
         if ($provider['address']['postal_code']) {
             $query = new Query\Provider(Query\Base::REPLACE);
             $query->addValues([
                 'source' => $source,
                 'id' => $provider['id'],
                 'name' => $provider['name'],
                 'display_name' => $provider['displayName'] ?? $provider['name'],
                 'contact__city' => $provider['address']['city'],
-                'contact__country' => $provider['address']['city'],
+                'contact__country' => $provider['address']['country'] ?? '',
                 'contact__lat' => $provider['geo']['lat'],
                 'contact__lon' => $provider['geo']['lon'],
                 'contact__postalCode' => intval($provider['address']['postal_code']),
-                'contact__region' => $provider['address']['city'],
+                'contact__region' => $provider['address']['region'] ?? '',
                 'contact__street' => $provider['address']['street'],
                 'contact__streetNumber' => $provider['address']['house_number'],
                 'link' => ('dldb' == $source)
                     ? 'https://service.berlin.de/standort/' . $provider['id'] . '/'
                     : ((isset($provider['link'])) ? $provider['link'] : ''),
                 'data' => json_encode($provider)
             ]);
             $this->writeItem($query);
-            $provider = $this->readEntity($source, $provider['id'], 0, true);
+            $entity = $this->readEntity($source, $provider['id'], 0, true);
+            return $entity;
         }
-        return $provider;
+        return null;
     }

175-195: Fix invalidation: wrong cache keys and overly strict parameter type

  • Deletes request-* instead of provider-*.
  • Typehint causes TypeError when a non-Provider is passed during imports/cleanup.

Apply:

-    public function removeCache(Entity $provider)
+    public function removeCache($provider): void
     {
         if (!App::$cache) {
             return;
         }
-
-        $source = $provider->getSource();
-        $providerId = $provider->getId();
-
-        if (App::$cache->has("request-$source-$providerId-0")) {
-            App::$cache->delete("request-$source-$providerId-0");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-1")) {
-            App::$cache->delete("request-$source-$providerId-1");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-2")) {
-            App::$cache->delete("request-$source-$providerId-2");
-        }
+        // Resolve identifiers
+        if ($provider instanceof Entity) {
+            $source = $provider->getSource();
+            $providerId = $provider->getId();
+        } elseif (is_array($provider)) {
+            $source = $provider['source'] ?? 'dldb';
+            $providerId = (int) ($provider['id'] ?? 0);
+        } else {
+            return;
+        }
+        foreach ([0, 1, 2] as $lvl) {
+            $key = "provider-$source-$providerId-$lvl";
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
     }
🧹 Nitpick comments (2)
zmsdb/src/Zmsdb/Provider.php (2)

110-111: Duplicate cache invalidation

writeEntity() already calls removeCache(). This second call is redundant.

-            $this->removeCache($provider);

11-17: Caching read-through looks fine; consider not caching empty entities

Optional: Align with Cluster/Source behavior and return null when entity not found to avoid caching empty shells.

Would you like a patch to add hasId() check and return null consistently?

Also applies to: 29-31

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e163cb8 and 6d42c0c.

📒 Files selected for processing (2)
  • zmsdb/src/Zmsdb/Provider.php (8 hunks)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Scope.php
🧬 Code graph analysis (2)
zmsdb/src/Zmsdb/Provider.php (7)
zmsapi/src/Zmsapi/Application.php (1)
  • Application (25-153)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Scope.php (2)
  • readEntity (21-45)
  • removeCache (655-684)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-50)
  • removeCache (172-189)
zmsdb/src/Zmsdb/Request.php (2)
  • readEntity (15-41)
  • removeCache (231-263)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-48)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (4)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Source.php (3)
  • readResolvedReferences (80-101)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (175-195)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (231-263)
  • readEntity (15-41)
zmsdb/src/Zmsdb/Config.php (1)
  • readEntity (14-30)

Comment on lines 673 to 683
if (App::$cache->has("scope-$scope->id-0")) {
App::$cache->delete("scope-$scope->id-0");
}

if (App::$cache->has("scope-$scope->id-1")) {
App::$cache->delete("scope-$scope->id-1");
}

if (App::$cache->has("scope-$scope->id-2")) {
App::$cache->delete("scope-$scope->id-2");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix cache key interpolation for object properties

"scope-$scope->id-*" in double quotes won’t interpolate as intended; use concatenation or braces.

-        if (App::$cache->has("scope-$scope->id-0")) {
-            App::$cache->delete("scope-$scope->id-0");
-        }
-
-        if (App::$cache->has("scope-$scope->id-1")) {
-            App::$cache->delete("scope-$scope->id-1");
-        }
-
-        if (App::$cache->has("scope-$scope->id-2")) {
-            App::$cache->delete("scope-$scope->id-2");
-        }
+        foreach ([0, 1, 2] as $lvl) {
+            $key = 'scope-' . $scope->id . '-' . $lvl;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (App::$cache->has("scope-$scope->id-0")) {
App::$cache->delete("scope-$scope->id-0");
}
if (App::$cache->has("scope-$scope->id-1")) {
App::$cache->delete("scope-$scope->id-1");
}
if (App::$cache->has("scope-$scope->id-2")) {
App::$cache->delete("scope-$scope->id-2");
}
foreach ([0, 1, 2] as $lvl) {
$key = 'scope-' . $scope->id . '-' . $lvl;
if (App::$cache->has($key)) {
App::$cache->delete($key);
}
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 673 to 683 the cache key strings use
"scope-$scope->id-*" which does not interpolate the object property; replace
them with either concatenation (e.g. 'scope-' . $scope->id . '-0') or brace
syntax inside double quotes (e.g. "scope-{$scope->id}-0") for each of the three
keys so the correct id value is used when calling App::$cache->has and
App::$cache->delete.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsdb/src/Zmsdb/Scope.php (1)

260-268: Propagate $disableCache to readResolvedReferences.

The $disableCache flag is not passed to readResolvedReferences at line 264, which means nested reads (DayOff, Closure) won't respect the cache bypass setting.

Apply this diff:

         if (count($result)) {
             foreach ($result as $entity) {
                 if ($entity instanceof Entity) {
-                    $entity = $this->readResolvedReferences($entity, $resolveReferences);
+                    $entity = $this->readResolvedReferences($entity, $resolveReferences, $disableCache);
                     $scopeList->addEntity($entity);
                 }
             }
♻️ Duplicate comments (1)
zmsdb/src/Zmsdb/Scope.php (1)

673-683: Fix cache key interpolation for object properties.

String interpolation with object properties in double quotes doesn't work as expected: "scope-$scope->id-0" will not interpolate $scope->id correctly. Use concatenation or brace syntax.

Apply this diff:

-        if (App::$cache->has("scope-$scope->id-0")) {
-            App::$cache->delete("scope-$scope->id-0");
-        }
-
-        if (App::$cache->has("scope-$scope->id-1")) {
-            App::$cache->delete("scope-$scope->id-1");
-        }
-
-        if (App::$cache->has("scope-$scope->id-2")) {
-            App::$cache->delete("scope-$scope->id-2");
-        }
+        foreach ([0, 1, 2] as $lvl) {
+            $key = 'scope-' . $scope->id . '-' . $lvl;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
🧹 Nitpick comments (4)
zmsdb/src/Zmsdb/Scope.php (4)

162-174: Consider propagating $disableCache parameter.

The readByRequestId method calls readByProviderId without a $disableCache parameter, which means it cannot bypass caching when needed. For consistency with other read methods, consider adding a $disableCache parameter to this method and propagating it through.

Example signature:

-    public function readByRequestId($requestId, $source, $resolveReferences = 0)
+    public function readByRequestId($requestId, $source, $resolveReferences = 0, $disableCache = false)
     {
         $scopeList = new Collection();
         $providerList = (new Provider())->readListBySource($source, 0, true, $requestId);
 
         foreach ($providerList as $provider) {
-            $scopeListByProvider = $this->readByProviderId($provider->getId(), $resolveReferences);
+            $scopeListByProvider = $this->readByProviderId($provider->getId(), $resolveReferences, $disableCache);

11-14: Consider addressing suppressed warnings.

The class suppresses multiple static analysis warnings including Complexity and TooManyMethods. While this is not introduced by the current changes, consider breaking down this class in future refactoring to improve maintainability.


19-19: Remove legacy self::$cache from Scope
The static $cache property in Scope is only ever cleared (no reads) and tests reset it, while actual caching uses App::$cache. Remove the unused $cache declaration and its self::$cache = [] calls in writeEntity, updateEntity, updateEmergency, and deleteEntity to clean up dead code.


673-683: Use explicit concatenation for cache keys
Switch from string interpolation to concatenation for clarity and to avoid interpolation pitfalls:

if (App::$cache->has('scope-' . $scope->id . '-0')) {
    App::$cache->delete('scope-' . $scope->id . '-0');
}
if (App::$cache->has('scope-' . $scope->id . '-1')) {
    App::$cache->delete('scope-' . $scope->id . '-1');
}
if (App::$cache->has('scope-' . $scope->id . '-2')) {
    App::$cache->delete('scope-' . $scope->id . '-2');
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d42c0c and d4220ac.

📒 Files selected for processing (1)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Scope.php
🧬 Code graph analysis (1)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (5)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • readList (82-99)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Department.php (5)
  • readResolvedReferences (50-61)
  • readByScopeId (81-90)
  • readList (63-79)
  • removeCache (413-430)
  • readEntity (22-48)
zmsdb/src/Zmsdb/Source.php (4)
  • readResolvedReferences (80-101)
  • readList (57-73)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (175-195)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (231-263)
  • readEntity (15-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / zmsapi-test
🔇 Additional comments (7)
zmsdb/src/Zmsdb/Scope.php (7)

7-7: LGTM!

The import enables clean access to the application-wide cache instance. Consistent with other files in the codebase.


39-41: LGTM!

Cache write is properly guarded against null cache instance.


47-57: LGTM!

The $disableCache parameter is properly added with a default value and correctly propagated to nested readByScopeId calls.


84-86: LGTM!

The cache write correctly guards against both null cache and non-cacheable PDOStatement objects.


148-152: LGTM!

The $disableCache parameter is correctly propagated to readResolvedReferences.


457-457: LGTM!

Cache invalidation is correctly invoked after all mutations. This ensures consistency between cached and persisted data.

Also applies to: 480-480, 497-497, 519-519, 542-542, 636-636, 652-652


661-671: Fix cache key interpolation for object properties.

Cache keys using "scopeReadByProviderId-$scope->getProviderId()-*" won't interpolate correctly. PHP doesn't parse method calls in double-quoted strings. Use concatenation instead.

Apply this diff:

-        if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
-            App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
+        $providerId = $scope->getProviderId();
+        foreach ([0, 1, 2] as $lvl) {
+            $key = 'scopeReadByProviderId-' . $providerId . '-' . $lvl;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
         }
-
-        if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
-            App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
-        }
-
-        if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
-            App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
-        }

Likely an incorrect or invalid review comment.

Comment on lines +23 to +29
$cacheKey = "scope-$scopeId-$resolveReferences";

if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$scope = App::$cache->get($cacheKey);
}

if (empty($scope)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Initialize $scope before use.

The variable $scope is read from cache at line 26 but is never initialized beforehand. If the cache check at line 25 fails, PHP will create $scope as null implicitly, but this is not explicit and may trigger warnings depending on error reporting levels.

Apply this diff:

     public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
     {
         $cacheKey = "scope-$scopeId-$resolveReferences";
+        $scope = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $scope = App::$cache->get($cacheKey);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$cacheKey = "scope-$scopeId-$resolveReferences";
if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$scope = App::$cache->get($cacheKey);
}
if (empty($scope)) {
public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
{
$cacheKey = "scope-$scopeId-$resolveReferences";
$scope = null;
if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$scope = App::$cache->get($cacheKey);
}
if (empty($scope)) {
// …
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 23 to 29, $scope is read from the
cache but never explicitly initialized which can trigger notices; initialize the
variable before the cache lookup (e.g. set $scope = null;) so it is defined
regardless of the cache branch, then keep the existing cache-get and empty-check
logic as-is.

Comment on lines +117 to +123
$cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";

if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$result = App::$cache->get($cacheKey);
}

if (empty($result)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Initialize $result before use.

Same issue as in previous methods: $result needs explicit initialization.

Apply this diff:

     {
         $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
+        $result = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $result = App::$cache->get($cacheKey);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$result = App::$cache->get($cacheKey);
}
if (empty($result)) {
$cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
$result = null;
if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$result = App::$cache->get($cacheKey);
}
if (empty($result)) {
// ...
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 117 to 123, $result is referenced
before being initialized which can cause undefined variable notices; initialize
$result (e.g., set $result = null or an empty array) before the cacheKey and
cache-check block so subsequent checks and assignments operate on a defined
variable.

Comment on lines +243 to +249
$cacheKey = "scopeReadList-$resolveReferences";

if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
$result = App::$cache->get($cacheKey);
}

if (empty($result)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Initialize $result before use.

Same pattern issue: $result needs explicit initialization.

Apply this diff:

     {
         $cacheKey = "scopeReadList-$resolveReferences";
+        $result = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $result = App::$cache->get($cacheKey);
         }
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 243 to 249, $result may be referenced
before assignment; ensure $result is explicitly initialized (for example to null
or an empty array) before the cache-check block so subsequent checks like
empty($result) are safe; add a single initialization statement immediately
before $cacheKey is set.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
zmsdb/src/Zmsdb/Request.php (1)

187-202: Add cache invalidation before cache-bypassed read.

While line 201 uses cache bypass to get fresh data, the old cache entries for this request are never invalidated. If readEntity() is called elsewhere without cache bypass, it will return stale data. Add a removeCache call or construct a minimal entity for invalidation.

Apply this diff:

         $this->writeItem($query);
+        
+        // Invalidate cache before reading fresh data
+        $entity = new Entity(['source' => $source, 'id' => $request['id']]);
+        $this->removeCache($entity);
+        
         return $this->readEntity($source, $request['id'], 0, true);
     }
♻️ Duplicate comments (2)
zmsdb/src/Zmsdb/Provider.php (2)

123-149: Ensure writeImportEntity always returns a Provider entity.

This method may return a mixed type - either an array (the input $provider parameter when the condition at line 125 is false) or an Entity (when the condition is true and line 146 executes). Downstream code, including cache invalidation, expects a consistent Provider entity with getSource() and getId() methods.

Apply this diff to always return an entity or null:

     public function writeImportEntity($provider, $source = 'dldb')
     {
         if ($provider['address']['postal_code']) {
             $query = new Query\Provider(Query\Base::REPLACE);
             $query->addValues([
                 'source' => $source,
                 'id' => $provider['id'],
                 'name' => $provider['name'],
                 'display_name' => $provider['displayName'] ?? $provider['name'],
                 'contact__city' => $provider['address']['city'],
                 'contact__country' => $provider['address']['city'],
                 'contact__lat' => $provider['geo']['lat'],
                 'contact__lon' => $provider['geo']['lon'],
                 'contact__postalCode' => intval($provider['address']['postal_code']),
                 'contact__region' => $provider['address']['city'],
                 'contact__street' => $provider['address']['street'],
                 'contact__streetNumber' => $provider['address']['house_number'],
                 'link' => ('dldb' == $source)
                     ? 'https://service.berlin.de/standort/' . $provider['id'] . '/'
                     : ((isset($provider['link'])) ? $provider['link'] : ''),
                 'data' => json_encode($provider)
             ]);
             $this->writeItem($query);
-            $provider = $this->readEntity($source, $provider['id'], 0, true);
+            return $this->readEntity($source, $provider['id'], 0, true);
         }
-        return $provider;
+        return null;
     }

175-195: Critical: removeCache() deletes wrong cache keys and has overly strict parameter type.

Two critical issues:

  1. Wrong keys: The method deletes "request-$source-$providerId-{0,1,2}" keys instead of "provider-$source-$providerId-{0,1,2}" keys, breaking cache invalidation entirely.
  2. Type error: The strict Entity typehint causes TypeError when called from writeImportEntity (line 148) which may pass an array.

Apply this diff:

-    public function removeCache(Entity $provider)
+    public function removeCache($provider): void
     {
         if (!App::$cache) {
             return;
         }
-
-        $source = $provider->getSource();
-        $providerId = $provider->getId();
-
-        if (App::$cache->has("request-$source-$providerId-0")) {
-            App::$cache->delete("request-$source-$providerId-0");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-1")) {
-            App::$cache->delete("request-$source-$providerId-1");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-2")) {
-            App::$cache->delete("request-$source-$providerId-2");
-        }
+        
+        // Resolve identifiers from entity or array
+        if ($provider instanceof Entity) {
+            $source = $provider->getSource();
+            $providerId = $provider->getId();
+        } elseif (is_array($provider)) {
+            $source = $provider['source'] ?? 'dldb';
+            $providerId = (int) ($provider['id'] ?? 0);
+        } else {
+            return;
+        }
+        
+        foreach ([0, 1, 2] as $lvl) {
+            $key = "provider-$source-$providerId-$lvl";
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
     }
🧹 Nitpick comments (5)
zmsdb/src/Zmsdb/Provider.php (2)

51-64: Consider renaming the query method for consistency.

While renaming the parameter to $providerIdCsv improves clarity, the query method is still named addConditionRequestCsv, which creates a naming inconsistency. Consider updating the query method name to addConditionProviderCsv for better alignment.


105-112: Remove redundant cache invalidation.

Line 110 calls removeCache($provider), but writeEntity() at line 109 already calls removeCache() internally. This duplicate invalidation is unnecessary.

Apply this diff:

     public function writeListBySource(\BO\Zmsentities\Source $source)
     {
         $this->writeDeleteListBySource($source->getSource());
         foreach ($source->getProviderList() as $provider) {
             $this->writeEntity($provider);
-            $this->removeCache($provider);
         }
         return $this->readListBySource($source->getSource());
     }
zmsdb/src/Zmsdb/Request.php (3)

155-175: Consider using cache bypass after write for consistency.

Line 174 reads the entity without cache bypass (disableCache=true), unlike the Provider implementation (line 102 in Provider.php). For consistency and to ensure fresh data after a write, consider using cache bypass.

Apply this diff:

         $this->writeItem($query);
 
         $this->removeCache($entity);
 
-        return $this->readEntity($entity->getSource(), $entity->getId());
+        return $this->readEntity($entity->getSource(), $entity->getId(), 0, true);
     }

177-185: Remove redundant cache invalidation.

Line 182 calls removeCache($request), but writeEntity() at line 181 already calls removeCache() internally. This duplicate invalidation is unnecessary.

Apply this diff:

     public function writeListBySource(\BO\Zmsentities\Source $source)
     {
         $this->writeDeleteListBySource($source->getSource());
         foreach ($source->getRequestList() as $request) {
             $this->writeEntity($request);
-            $this->removeCache($request);
         }
         return $this->readListBySource($source->getSource());
     }

227-259: Consider relaxing the parameter type for consistency.

While the cache keys are correct for Request entities, the strict Entity typehint could cause issues in edge cases where the method might be called with array data. For consistency with the suggested Provider.php fix and defensive coding, consider accepting mixed types.

Apply this diff:

-    public function removeCache(Entity $request)
+    public function removeCache($request): void
     {
         if (!App::$cache) {
             return;
         }
 
-        $source = $request->getSource();
-        $requestId = $request->getId();
+        // Resolve identifiers from entity or array
+        if ($request instanceof Entity) {
+            $source = $request->getSource();
+            $requestId = $request->getId();
+        } elseif (is_array($request)) {
+            $source = $request['source'] ?? 'dldb';
+            $requestId = $request['id'] ?? '';
+        } else {
+            return;
+        }
 
         if (App::$cache->has("request-$source-$requestId-0")) {
             App::$cache->delete("request-$source-$requestId-0");
         }
 
         if (App::$cache->has("request-$source-$requestId-1")) {
             App::$cache->delete("request-$source-$requestId-1");
         }
 
         if (App::$cache->has("request-$source-$requestId-2")) {
             App::$cache->delete("request-$source-$requestId-2");
         }
 
         if (App::$cache->has("requestReadListBySource-$source-0")) {
             App::$cache->delete("requestReadListBySource-$source-0");
         }
 
         if (App::$cache->has("requestReadListBySource-$source-1")) {
             App::$cache->delete("requestReadListBySource-$source-1");
         }
 
         if (App::$cache->has("requestReadListBySource-$source-2")) {
             App::$cache->delete("requestReadListBySource-$source-2");
         }
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4220ac and dfaa304.

📒 Files selected for processing (2)
  • zmsdb/src/Zmsdb/Provider.php (7 hunks)
  • zmsdb/src/Zmsdb/Request.php (8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Request.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Request.php
🧬 Code graph analysis (2)
zmsdb/src/Zmsdb/Provider.php (5)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Provider.php (2)
  • Provider (5-104)
  • getSource (64-67)
zmsdb/src/Zmsdb/Query/Provider.php (2)
  • Provider (5-86)
  • addConditionRequestCsv (63-77)
zmsdb/src/Zmsdb/Request.php (2)
  • readEntity (15-41)
  • removeCache (227-259)
zmsdb/src/Zmsdb/Source.php (3)
  • readEntity (25-50)
  • removeCache (172-189)
  • Source (18-190)
zmsdb/src/Zmsdb/Request.php (8)
zmsapi/src/Zmsapi/Application.php (1)
  • Application (25-153)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Provider.php (3)
  • readEntity (11-34)
  • testSource (168-173)
  • removeCache (175-195)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-50)
  • removeCache (172-189)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-48)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Scope.php (3)
  • readEntity (21-45)
  • testSource (223-228)
  • removeCache (655-684)
zmsentities/src/Zmsentities/Request.php (2)
  • Request (5-83)
  • getSource (44-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (zmscitizenview)
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (6)
zmsdb/src/Zmsdb/Provider.php (3)

5-5: LGTM!

The Application alias follows the established pattern used consistently across the codebase for cache access.


11-34: LGTM!

The caching implementation correctly:

  • Guards cache access with App::$cache check
  • Uses has() before get() to avoid false negatives
  • Implements read-through cache pattern consistently with other modules

99-102: LGTM!

Cache invalidation followed by cache-bypassed read ensures consistency and prevents stale data.

zmsdb/src/Zmsdb/Request.php (3)

5-5: LGTM!

The Application alias follows the established pattern consistently.


15-41: LGTM!

The caching implementation correctly guards cache access and uses has() before get() to properly handle falsy cached values.


111-132: LGTM!

The list caching implementation is consistent with the entity-level caching and follows best practices.

Comment on lines 151 to 159
public function writeDeleteEntity($providerId, $source)
{
$provider = $this->readEntity($source, $providerId);
$query = new Query\Provider(Query\Base::DELETE);
$query->addConditionProviderId($providerId);
$query->addConditionProviderSource($source);
$this->removeCache($provider);
return $this->deleteItem($query);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle non-existent provider in writeDeleteEntity.

Line 153 calls readEntity() which throws an exception if the provider doesn't exist. This prevents the deletion query from executing for non-existent providers, which could cause issues in cleanup scenarios.

Consider one of these approaches:

     public function writeDeleteEntity($providerId, $source)
     {
-        $provider = $this->readEntity($source, $providerId);
+        try {
+            $provider = $this->readEntity($source, $providerId);
+            $this->removeCache($provider);
+        } catch (\Exception $e) {
+            // Provider doesn't exist, still proceed with deletion attempt
+        }
         $query = new Query\Provider(Query\Base::DELETE);
         $query->addConditionProviderId($providerId);
         $query->addConditionProviderSource($source);
-        $this->removeCache($provider);
         return $this->deleteItem($query);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function writeDeleteEntity($providerId, $source)
{
$provider = $this->readEntity($source, $providerId);
$query = new Query\Provider(Query\Base::DELETE);
$query->addConditionProviderId($providerId);
$query->addConditionProviderSource($source);
$this->removeCache($provider);
return $this->deleteItem($query);
}
public function writeDeleteEntity($providerId, $source)
{
try {
$provider = $this->readEntity($source, $providerId);
$this->removeCache($provider);
} catch (\Exception $e) {
// Provider doesn't exist, still proceed with deletion attempt
}
$query = new Query\Provider(Query\Base::DELETE);
$query->addConditionProviderId($providerId);
$query->addConditionProviderSource($source);
return $this->deleteItem($query);
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Provider.php around lines 151 to 159, readEntity() is called
and will throw if the provider does not exist, preventing the deletion query
from running; change this by calling readEntity() inside a try/catch (or by
checking for a null/false return if readEntity is changed to return null) and
only call removeCache($provider) when a provider object was successfully
returned, but always construct the DELETE Query\Provider and call
deleteItem($query) so deletions proceed even when the entity was missing; ensure
the method returns the deleteItem result and does not rethrow the not-found
exception.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
zmsdb/src/Zmsdb/Scope.php (1)

675-685: Fix cache key interpolation to avoid fatal parse error.

Inside double-quoted strings PHP cannot dereference $scope->id; this code will crash at load time. Build the keys via concatenation (or braces) before calling the cache API.

Apply this diff:

-        if (App::$cache->has("scope-$scope->id-0")) {
-            App::$cache->delete("scope-$scope->id-0");
-        }
-
-        if (App::$cache->has("scope-$scope->id-1")) {
-            App::$cache->delete("scope-$scope->id-1");
-        }
-
-        if (App::$cache->has("scope-$scope->id-2")) {
-            App::$cache->delete("scope-$scope->id-2");
-        }
+        foreach ([0, 1, 2] as $level) {
+            $key = 'scope-' . $scope->id . '-' . $level;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfaa304 and 495b688.

📒 Files selected for processing (1)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Scope.php
🧬 Code graph analysis (1)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (5)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • readList (82-99)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Source.php (4)
  • readResolvedReferences (80-101)
  • readList (57-73)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/Department.php (5)
  • readResolvedReferences (50-61)
  • readByScopeId (81-90)
  • readList (63-79)
  • removeCache (413-430)
  • readEntity (22-48)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (175-195)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (227-259)
  • readEntity (15-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: call-php-unit-tests / zmsdb-test

Comment on lines 655 to 686
public function removeCache($scope)
{
if (!App::$cache) {
return;
}

if (isset($scope->provider)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}

if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}

if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}

if (App::$cache->has("scope-$scope->id-0")) {
App::$cache->delete("scope-$scope->id-0");
}

if (App::$cache->has("scope-$scope->id-1")) {
App::$cache->delete("scope-$scope->id-1");
}

if (App::$cache->has("scope-$scope->id-2")) {
App::$cache->delete("scope-$scope->id-2");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Extend cache invalidation to cover cluster/list keys.

removeCache() only deletes provider and per-scope entries, leaving scopeReadByClusterId-* and scopeReadList-* caches stale after writes/updates/deletes. Add deletions for those keys (iterate over resolve levels as above and use the scope’s cluster identifier) so downstream reads don’t serve outdated data.

Apply this diff for the list keys, and add corresponding cluster-key invalidation using the available cluster identifier on the scope entity:

         if (isset($scope->provider)) {
             …
         }

+        foreach ([0, 1, 2] as $level) {
+            $listKey = 'scopeReadList-' . $level;
+            if (App::$cache->has($listKey)) {
+                App::$cache->delete($listKey);
+            }
+        }

(Remember to delete the scopeReadByClusterId-<clusterId>-* entries as well, using whichever accessor (getClusterId() or property) exposes the scope’s cluster id.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function removeCache($scope)
{
if (!App::$cache) {
return;
}
if (isset($scope->provider)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}
if (App::$cache->has("scope-$scope->id-0")) {
App::$cache->delete("scope-$scope->id-0");
}
if (App::$cache->has("scope-$scope->id-1")) {
App::$cache->delete("scope-$scope->id-1");
}
if (App::$cache->has("scope-$scope->id-2")) {
App::$cache->delete("scope-$scope->id-2");
}
}
public function removeCache($scope)
{
if (!App::$cache) {
return;
}
if (isset($scope->provider)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}
foreach ([0, 1, 2] as $level) {
$listKey = 'scopeReadList-' . $level;
if (App::$cache->has($listKey)) {
App::$cache->delete($listKey);
}
}
if (App::$cache->has("scope-$scope->id-0")) {
App::$cache->delete("scope-$scope->id-0");
}
if (App::$cache->has("scope-$scope->id-1")) {
App::$cache->delete("scope-$scope->id-1");
}
if (App::$cache->has("scope-$scope->id-2")) {
App::$cache->delete("scope-$scope->id-2");
}
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 655 to 686, removeCache currently only
deletes provider-based and per-scope cache keys but misses the
scopeReadByClusterId-* and scopeReadList-* entries; update the method to also
delete scopeReadList-<scopeId>-<level> and
scopeReadByClusterId-<clusterId>-<level> for the same resolve levels (0,1,2)
used elsewhere. Use the scope's cluster identifier via the appropriate accessor
(e.g. $scope->getClusterId() or $scope->clusterId) and iterate levels 0..2 to
call App::$cache->has(...) and App::$cache->delete(...) for each constructed
key, mirroring the existing pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
zmsdb/src/Zmsdb/Scope.php (5)

243-249: Initialize $result before use.

The variable $result should be initialized before the cache check block to prevent undefined variable notices.

Apply this diff:

     {
         $cacheKey = "scopeReadList-$resolveReferences";
+        $result = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $result = App::$cache->get($cacheKey);
         }

117-123: Initialize $result before use.

The variable $result needs explicit initialization before the cache check to avoid undefined variable notices.

Apply this diff:

     {
         $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
+        $result = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $result = App::$cache->get($cacheKey);
         }

655-686: Extend cache invalidation to cover cluster and list keys.

The removeCache() method only deletes provider-based and per-scope cache keys, leaving scopeReadByClusterId-* and scopeReadList-* caches stale after write/update/delete operations. This can serve outdated data on subsequent reads.

Apply this diff to also invalidate cluster and list cache keys:

         if (App::$cache->has("scope-$scope->id-2")) {
             App::$cache->delete("scope-$scope->id-2");
         }
+
+        // Invalidate cluster-based cache entries
+        if (isset($scope->cluster) && isset($scope->cluster->id)) {
+            foreach ([0, 1, 2] as $level) {
+                $clusterKey = 'scopeReadByClusterId-' . $scope->cluster->id . '-' . $level;
+                if (App::$cache->has($clusterKey)) {
+                    App::$cache->delete($clusterKey);
+                }
+            }
+        }
+
+        // Invalidate list cache entries
+        foreach ([0, 1, 2] as $level) {
+            $listKey = 'scopeReadList-' . $level;
+            if (App::$cache->has($listKey)) {
+                App::$cache->delete($listKey);
+            }
+        }
     }

64-70: Initialize $result before use.

Similar to readEntity, the variable $result is assigned at line 67 only when the cache check passes, but is referenced at line 70 without prior initialization.

Apply this diff:

     {
         $cacheKey = "scopeReadByClusterId-$clusterId-$resolveReferences";
+        $result = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $result = App::$cache->get($cacheKey);
         }

23-29: Initialize $scope before use.

The variable $scope is assigned at line 26 only if the cache check passes, but line 29 references it without initialization. If the cache condition fails, this causes an undefined variable notice.

Apply this diff:

     public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
     {
         $cacheKey = "scope-$scopeId-$resolveReferences";
+        $scope = null;
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $scope = App::$cache->get($cacheKey);
         }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 495b688 and e140b05.

📒 Files selected for processing (1)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Scope.php
🧬 Code graph analysis (1)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (5)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • readList (82-99)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Department.php (5)
  • readResolvedReferences (50-61)
  • readByScopeId (81-90)
  • readList (63-79)
  • removeCache (413-430)
  • readEntity (22-48)
zmsdb/src/Zmsdb/Source.php (4)
  • readResolvedReferences (80-101)
  • readList (57-73)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (175-195)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (227-259)
  • readEntity (15-41)
🔇 Additional comments (4)
zmsdb/src/Zmsdb/Scope.php (4)

7-7: LGTM!

The import alias for Application as App is clean and consistent with the caching pattern used throughout this PR.


47-57: LGTM!

The addition of the $disableCache parameter and its propagation to nested reads (DayOff, Closure) correctly implements the cache bypass mechanism.


457-457: LGTM!

Cache invalidation via removeCache() is correctly invoked after all mutation operations (writeEntity, updateEntity, replacePreferences, updateGhostWorkstationCount, updateEmergency, deleteEntity, deletePreferences), ensuring cache consistency.

Also applies to: 480-480, 497-497, 519-519, 542-542, 636-636, 652-652


675-685: LGTM!

The cache key construction using string interpolation ("scope-$scope->id-0") is correct and consistent with patterns used in other repository classes (Cluster, Department, etc.).

Comment on lines +661 to +673
if (isset($scope->provider) && isset($this->provider->id)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}

if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}

if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix incorrect property access in provider check.

Line 661 checks isset($this->provider->id), which incorrectly references the Scope repository class instead of the $scope entity parameter. This causes the provider-related cache invalidation to fail or behave incorrectly.

Apply this diff:

-        if (isset($scope->provider) && isset($this->provider->id)) {
+        if (isset($scope->provider) && isset($scope->provider->id)) {
             if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
                 App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
             }

Or simplify to:

-        if (isset($scope->provider) && isset($this->provider->id)) {
+        if (isset($scope->provider)) {
             if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {

since getProviderId() is called anyway.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isset($scope->provider) && isset($this->provider->id)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}
// In removeCache(): only invalidate when the scope entity actually has a provider
if (isset($scope->provider)) {
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
}
if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
}
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Scope.php around lines 661-673, the condition uses
isset($this->provider->id) which incorrectly references the repository instead
of the passed $scope entity; change that to check the entity (either use
isset($scope->provider->id) or simply check if $scope->getProviderId() !== null)
so the provider-related cache invalidation runs against the correct object, then
keep the existing cache delete calls using $scope->getProviderId().

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
zmsdb/src/Zmsdb/Request.php (1)

15-41: Initialize $request before use.

Similar to Department.php, the variable $request should be initialized to avoid potential undefined variable notices.

Apply this diff:

 public function readEntity($source, $requestId, $resolveReferences = 0, $disableCache = false)
 {
     $cacheKey = "request-$source-$requestId-$resolveReferences";
+    $request = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         return App::$cache->get($cacheKey);
     }

Note: Since this method returns early on cache hit, the initialization may not be strictly necessary here, but it's good defensive practice.

zmsdb/src/Zmsdb/Scope.php (1)

21-45: Initialize $scope before use.

The variable $scope is not initialized before line 26, which can cause undefined variable notices when the cache check doesn't execute.

Apply this diff:

 public function readEntity($scopeId, $resolveReferences = 0, $disableCache = false)
 {
     $cacheKey = "scope-$scopeId-$resolveReferences";
+    $scope = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         $scope = App::$cache->get($cacheKey);
     }
♻️ Duplicate comments (5)
zmsdb/src/Zmsdb/Scope.php (4)

115-160: Initialize $result before use.

Same issue as readByClusterId: $result needs initialization.

Apply this diff:

 public function readByProviderId($providerId, $resolveReferences = 0, $disableCache = false)
 {
     $cacheKey = "scopeReadByProviderId-$providerId-$resolveReferences";
+    $result = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         $result = App::$cache->get($cacheKey);
     }

241-270: Initialize $result before use.

Same pattern: $result needs initialization before the cache check.

Apply this diff:

 public function readList($resolveReferences = 0, $disableCache = false)
 {
     $cacheKey = "scopeReadList-$resolveReferences";
+    $result = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         $result = App::$cache->get($cacheKey);
     }

59-113: Initialize $result before use.

The variable $result is not initialized before line 67, which can cause undefined variable notices.

Apply this diff:

 public function readByClusterId(
     $clusterId,
     $resolveReferences = 0,
     $disableCache = false
 ) {
     $cacheKey = "scopeReadByClusterId-$clusterId-$resolveReferences";
+    $result = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         $result = App::$cache->get($cacheKey);
     }

655-688: Critical: Fix incorrect property access in removeCache.

Line 661 accesses $this->provider->id (the repository class property) instead of $scope->provider->id (the entity parameter property). This breaks provider-related cache invalidation.

Apply this diff:

-    if (isset($scope->provider) && isset($this->provider->id)) {
+    if (isset($scope->provider) && isset($scope->provider->id)) {
         if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
             App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
         }

Or simplify since getProviderId() is called anyway:

-    if (isset($scope->provider) && isset($this->provider->id)) {
+    if (isset($scope->provider)) {
         if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
zmsdb/src/Zmsdb/Provider.php (1)

175-195: Critical: Fix copy-paste errors in removeCache.

Two critical bugs prevent proper cache invalidation:

  1. Line 177 checks $department->id instead of $provider->id
  2. Lines 184-194 delete request-* keys instead of provider-* keys

Apply this diff:

 public function removeCache(Entity $provider)
 {
-    if (!App::$cache || !isset($department->id)) {
+    if (!App::$cache || !isset($provider->id)) {
         return;
     }

     $source = $provider->getSource();
     $providerId = $provider->getId();

-    if (App::$cache->has("request-$source-$providerId-0")) {
-        App::$cache->delete("request-$source-$providerId-0");
+    if (App::$cache->has("provider-$source-$providerId-0")) {
+        App::$cache->delete("provider-$source-$providerId-0");
     }

-    if (App::$cache->has("request-$source-$providerId-1")) {
-        App::$cache->delete("request-$source-$providerId-1");
+    if (App::$cache->has("provider-$source-$providerId-1")) {
+        App::$cache->delete("provider-$source-$providerId-1");
     }

-    if (App::$cache->has("request-$source-$providerId-2")) {
-        App::$cache->delete("request-$source-$providerId-2");
+    if (App::$cache->has("provider-$source-$providerId-2")) {
+        App::$cache->delete("provider-$source-$providerId-2");
     }
 }
🧹 Nitpick comments (1)
zmsdb/src/Zmsdb/Department.php (1)

413-430: Fix copy-paste error: use $department consistently.

Line 415 references $department->id but the parameter is named $department, making this check correct by accident. However, looking at the pattern in other removeCache methods (Cluster.php line 440, etc.), the intent is clear. The implementation is actually correct but could add a null check for safety.

Consider adding an explicit null check for defensive programming:

 public function removeCache($department)
 {
-    if (!App::$cache || !isset($department->id)) {
+    if (!App::$cache || !$department || !isset($department->id)) {
         return;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e140b05 and 06026ea.

📒 Files selected for processing (5)
  • zmsdb/src/Zmsdb/Cluster.php (9 hunks)
  • zmsdb/src/Zmsdb/Department.php (6 hunks)
  • zmsdb/src/Zmsdb/Provider.php (7 hunks)
  • zmsdb/src/Zmsdb/Request.php (8 hunks)
  • zmsdb/src/Zmsdb/Scope.php (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Department.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Scope.php
  • zmsdb/src/Zmsdb/Provider.php
  • zmsdb/src/Zmsdb/Department.php
🧬 Code graph analysis (5)
zmsdb/src/Zmsdb/Cluster.php (3)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Department.php (3)
  • readEntity (22-48)
  • readResolvedReferences (50-61)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Scope.php (5)
  • readEntity (21-45)
  • readResolvedReferences (47-57)
  • Scope (17-689)
  • readByClusterId (59-113)
  • removeCache (655-688)
zmsdb/src/Zmsdb/Request.php (7)
zmsapi/src/Zmsapi/Application.php (1)
  • Application (25-153)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-48)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Provider.php (3)
  • readEntity (11-34)
  • testSource (168-173)
  • removeCache (175-195)
zmsdb/src/Zmsdb/Scope.php (3)
  • readEntity (21-45)
  • testSource (223-228)
  • removeCache (655-688)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-50)
  • removeCache (172-189)
zmsdb/src/Zmsdb/Scope.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (4)
  • readResolvedReferences (55-63)
  • readByScopeId (101-114)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Department.php (4)
  • readResolvedReferences (50-61)
  • readByScopeId (81-90)
  • removeCache (413-430)
  • readEntity (22-48)
zmsdb/src/Zmsdb/Source.php (3)
  • readResolvedReferences (80-101)
  • removeCache (172-189)
  • readEntity (25-50)
zmsdb/src/Zmsdb/DayOff.php (3)
  • DayOff (9-237)
  • readByScopeId (79-110)
  • removeCache (227-236)
zmsdb/src/Zmsdb/Closure.php (3)
  • readByScopeId (12-39)
  • Closure (10-117)
  • readEntity (109-116)
zmsdb/src/Zmsdb/Provider.php (2)
  • removeCache (175-195)
  • readEntity (11-34)
zmsdb/src/Zmsdb/Request.php (2)
  • removeCache (227-261)
  • readEntity (15-41)
zmsdb/src/Zmsdb/Provider.php (5)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsentities/src/Zmsentities/Provider.php (2)
  • Provider (5-104)
  • getSource (64-67)
zmsdb/src/Zmsdb/Query/Provider.php (2)
  • Provider (5-86)
  • addConditionRequestCsv (63-77)
zmsdb/src/Zmsdb/Cluster.php (1)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Request.php (1)
  • readEntity (15-41)
zmsdb/src/Zmsdb/Department.php (4)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Cluster.php (3)
  • readResolvedReferences (55-63)
  • removeCache (438-455)
  • readEntity (27-53)
zmsdb/src/Zmsdb/Scope.php (3)
  • readResolvedReferences (47-57)
  • removeCache (655-688)
  • readEntity (21-45)
zmsdb/src/Zmsdb/Source.php (3)
  • readResolvedReferences (80-101)
  • removeCache (172-189)
  • readEntity (25-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: call-php-unit-tests / zmsdb-test
  • GitHub Check: call-php-unit-tests / zmsapi-test
🔇 Additional comments (3)
zmsdb/src/Zmsdb/Cluster.php (2)

27-53: LGTM: Proper cache implementation with initialization.

The cache integration is correctly implemented:

  • Variable $cluster is initialized to null before use
  • Cache checks use proper has() before get()
  • Cache is guarded with null checks before set()
  • Cache bypass parameter is properly propagated

438-455: LGTM: Cache invalidation properly structured.

The removeCache() method correctly:

  • Guards against null cache and missing entity ID
  • Uses proper cache key format matching readEntity()
  • Covers all resolution levels (0, 1, 2)
zmsdb/src/Zmsdb/Department.php (1)

22-48: Initialize $department before cache lookup.

The variable $department is undefined when the cache check at line 26 doesn't execute or returns false. This can cause PHP notices when line 30 evaluates empty($department).

Apply this diff:

 public function readEntity($departmentId, $resolveReferences = 0, $disableCache = false)
 {
     $cacheKey = "department-$departmentId-$resolveReferences";
+    $department = null;

     if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
         $department = App::$cache->get($cacheKey);
     }

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
zmsdb/src/Zmsdb/Provider.php (1)

175-195: CRITICAL: removeCache deletes wrong cache keys and has overly strict type.

Lines 184-194 delete "request-*" keys instead of "provider-*" keys, invalidating the wrong entity type. Additionally, the strict Entity $provider typehint causes TypeError when called with non-Entity types (as shown in the past review comment pipeline error).

Apply this diff:

-    public function removeCache(Entity $provider)
+    public function removeCache($provider): void
     {
-        if (!App::$cache || !isset($provider->id)) {
+        if (!App::$cache) {
             return;
         }
 
-        $source = $provider->getSource();
-        $providerId = $provider->getId();
+        // Resolve identifiers from entity or array
+        if ($provider instanceof Entity) {
+            $source = $provider->getSource();
+            $providerId = $provider->getId();
+        } elseif (is_array($provider)) {
+            $source = $provider['source'] ?? 'dldb';
+            $providerId = (int) ($provider['id'] ?? 0);
+        } else {
+            return;
+        }
+        
+        if (!$providerId) {
+            return;
+        }
 
-        if (App::$cache->has("request-$source-$providerId-0")) {
-            App::$cache->delete("request-$source-$providerId-0");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-1")) {
-            App::$cache->delete("request-$source-$providerId-1");
-        }
-
-        if (App::$cache->has("request-$source-$providerId-2")) {
-            App::$cache->delete("request-$source-$providerId-2");
+        foreach ([0, 1, 2] as $level) {
+            $cacheKey = "provider-$source-$providerId-$level";
+            if (App::$cache->has($cacheKey)) {
+                App::$cache->delete($cacheKey);
+            }
         }
     }
🧹 Nitpick comments (1)
zmsdb/src/Zmsdb/Provider.php (1)

51-67: Clarify parameter naming for request-based filtering.

The parameter $providerIdCsv is misleading since line 63 calls addConditionRequestCsv($providerIdCsv, $source), which filters providers by request IDs (as seen in Query/Provider.php). Consider renaming to $requestIdCsv for clarity.

Apply this diff:

-    public function readListBySource($source, $resolveReferences = 0, $isAssigned = null, $providerIdCsv = null)
+    public function readListBySource($source, $resolveReferences = 0, $isAssigned = null, $requestIdCsv = null)
     {
         $this->testSource($source);
         $query = new Query\Provider(Query\Base::SELECT);
         $query->setDistinctSelect();
         $query->setResolveLevel($resolveReferences);
         $query->addEntityMapping();
         $query->addConditionProviderSource($source);
         if (null !== $isAssigned) {
             $query->addConditionIsAssigned($isAssigned);
         }
-        if (null !== $providerIdCsv) {
-            $query->addConditionRequestCsv($providerIdCsv, $source);
+        if (null !== $requestIdCsv) {
+            $query->addConditionRequestCsv($requestIdCsv, $source);
         }
         $providerList = $this->readCollection($query);
         return ($providerList->count()) ? $providerList->sortById() : $providerList;
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06026ea and 30cad00.

📒 Files selected for processing (2)
  • zmsdb/src/Zmsdb/Provider.php (7 hunks)
  • zmsdb/src/Zmsdb/Request.php (8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Provider.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Request.php
  • zmsdb/src/Zmsdb/Provider.php
🧬 Code graph analysis (2)
zmsdb/src/Zmsdb/Request.php (8)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Provider.php (3)
  • readEntity (11-34)
  • testSource (168-173)
  • removeCache (175-195)
zmsdb/src/Zmsdb/Cluster.php (2)
  • readEntity (27-53)
  • removeCache (438-455)
zmsdb/src/Zmsdb/Department.php (2)
  • readEntity (22-48)
  • removeCache (413-430)
zmsdb/src/Zmsdb/Scope.php (3)
  • readEntity (21-45)
  • testSource (223-228)
  • removeCache (655-688)
zmsdb/src/Zmsdb/Source.php (2)
  • readEntity (25-50)
  • removeCache (172-189)
zmsentities/src/Zmsentities/Request.php (2)
  • Request (5-83)
  • getSource (44-47)
zmsdb/src/Zmsdb/Query/Request.php (3)
  • Request (5-107)
  • addConditionRequestSource (83-87)
  • addConditionRequestId (35-39)
zmsdb/src/Zmsdb/Provider.php (5)
zmsapi/src/Zmsapi/Application.php (1)
  • Application (25-153)
zmsdb/src/Zmsdb/Application.php (1)
  • Application (19-100)
zmsdb/src/Zmsdb/Query/Provider.php (2)
  • Provider (5-86)
  • addConditionRequestCsv (63-77)
zmsdb/src/Zmsdb/Request.php (3)
  • readEntity (15-41)
  • readListBySource (111-132)
  • removeCache (227-261)
zmsdb/src/Zmsdb/Source.php (3)
  • readEntity (25-50)
  • removeCache (172-189)
  • Source (18-190)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: call-php-build-images / build (zmsticketprinter, 8.3)
  • GitHub Check: call-php-build-images / build (zmsadmin, 8.3)
  • GitHub Check: call-php-build-images / build (zmsapi, 8.3)
  • GitHub Check: call-php-build-images / build (zmsstatistic, 8.3)
  • GitHub Check: call-php-build-images / build (zmscitizenapi, 8.3)
  • GitHub Check: call-php-build-images / build (zmsmessaging, 8.3)
  • GitHub Check: call-php-build-images / build (zmscalldisplay, 8.3)

Comment on lines 204 to 211
public function writeDeleteEntity($requestId, $source)
{
$query = new Query\Request(Query\Base::DELETE);
$query->addConditionRequestId($requestId);
$query->addConditionRequestSource($source);

return $this->deleteItem($query);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add cache invalidation before deletion.

writeDeleteEntity should invalidate cache entries before deletion, similar to how Provider::writeDeleteEntity (lines 151-159) reads the entity and calls removeCache() before deletion. This ensures stale cache entries don't persist after a request is deleted.

Apply this diff:

 public function writeDeleteEntity($requestId, $source)
 {
+    try {
+        $request = $this->readEntity($source, $requestId, 0, true);
+        $this->removeCache($request);
+    } catch (\Exception $e) {
+        // Request doesn't exist, proceed with deletion anyway
+    }
     $query = new Query\Request(Query\Base::DELETE);
     $query->addConditionRequestId($requestId);
     $query->addConditionRequestSource($source);

     return $this->deleteItem($query);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function writeDeleteEntity($requestId, $source)
{
$query = new Query\Request(Query\Base::DELETE);
$query->addConditionRequestId($requestId);
$query->addConditionRequestSource($source);
return $this->deleteItem($query);
}
public function writeDeleteEntity($requestId, $source)
{
try {
$request = $this->readEntity($source, $requestId, 0, true);
$this->removeCache($request);
} catch (\Exception $e) {
// Request doesn't exist, proceed with deletion anyway
}
$query = new Query\Request(Query\Base::DELETE);
$query->addConditionRequestId($requestId);
$query->addConditionRequestSource($source);
return $this->deleteItem($query);
}
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Request.php around lines 204 to 211, before constructing and
executing the delete Query, load the entity using the existing read/get method
(same approach as Provider::writeDeleteEntity at lines 151-159), call
removeCache() on that entity if it exists to invalidate any cached entries, then
proceed to build the DELETE Query and call deleteItem($query); ensure null
checks around the read result to avoid errors when entity is missing.

}
}

App::$cache->set($cacheKey, $closureList);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if App::$cache

@manjencic manjencic changed the base branch from next to main October 17, 2025 07:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
zmsdb/src/Zmsdb/Calldisplay.php (1)

73-91: Add null safety check for $image.

The method can return null if both database queries fail, but there's no explicit handling for this case. Callers expecting an array with image data (including the now-missing mime field) may encounter null reference errors.

Consider adding a null check and either returning a default image structure or throwing an appropriate exception.

Example fix:

         if (! $image) {
             $image = $this->getReader()
             ->fetchOne((new Query\Calldisplay(Query\Base::SELECT))
                 ->getQueryImage(), ['name' => "logo.png"]);
         }
+        
+        if (! $image) {
+            // Return a safe default or throw an exception
+            return null; // Or throw new Exception\Calldisplay\ImageNotFound();
+        }

-        //$mime = pathinfo($image['name'], PATHINFO_EXTENSION);
-        //$image['mime'] = ($mime == 'jpg') ? 'jpeg' : $mime;
+        $mime = pathinfo($image['name'], PATHINFO_EXTENSION);
+        $image['mime'] = ($mime == 'jpg') ? 'jpeg' : $mime;
         return $image;
zmsdb/src/Zmsdb/Link.php (3)

16-43: Add type hints and extract cache key generation.

The caching implementation is functionally correct, but has the following maintainability concerns:

  1. Missing type hints for $departmentId parameter and return type
  2. Cache key string "linksReadByDepartmentId-$departmentId" is duplicated in writeEntity (line 61), violating DRY

Apply this diff to add type hints and extract cache key generation:

+    private function getCacheKey(int $departmentId): string
+    {
+        return "linksReadByDepartmentId-$departmentId";
+    }
+
     /**
      * Fetch status from db
      *
      * @return \BO\Zmsentities\Collection\LinkList
      */
-    public function readByDepartmentId($departmentId, $disableCache = false)
+    public function readByDepartmentId(int $departmentId, bool $disableCache = false): Collection
     {
-        $cacheKey = "linksReadByDepartmentId-$departmentId";
+        $cacheKey = $this->getCacheKey($departmentId);
 
         if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {

Then update writeEntity to use the same method:

     public function writeEntity(Entity $entity, $departmentId)
     {
         $query = new Query\Link(Query\Base::INSERT);
         $values = $query->reverseEntityMapping($entity, $departmentId);
         $query->addValues($values);
 
         if (App::$cache) {
-            App::$cache->delete("linksReadByDepartmentId-$departmentId");
+            App::$cache->delete($this->getCacheKey($departmentId));
         }
 
         return $this->writeItem($query);
     }

54-65: Add type hints to writeEntity.

The cache invalidation logic is correct, but the method signature lacks type hints for consistency and type safety.

Apply this diff:

-    public function writeEntity(Entity $entity, $departmentId)
+    public function writeEntity(Entity $entity, int $departmentId): int
     {

67-72: Critical: Missing cache invalidation in deleteEntity requires departmentId parameter.

The deleteEntity method performs a write operation without invalidating the cache. When a link is deleted via Department.php:285, the cache key based on departmentId (set by readByDepartmentId) is never cleared, causing deleted links to persist in cache until expiration.

The calling code in Department.php:281-285 has $departmentId available but cannot pass it to deleteEntity:

$existingLinks = (new Link())->readByDepartmentId($departmentId);
if ($existingLinks->count()) {
    foreach ($existingLinks as $item) {
        $query = new Link();
        $query->deleteEntity($item->getId());  // Missing departmentId
    }
}

Required changes:

  1. Link.php:67 – Add $departmentId parameter and invalidate cache:
-    public function deleteEntity($itemId)
+    public function deleteEntity($itemId, $departmentId)
     {
         $query = new Query\Link(Query\Base::DELETE);
         $query->addConditionLinkId($itemId);
+        
+        if (App::$cache) {
+            App::$cache->delete($this->getCacheKey($departmentId));
+        }
+        
         return ($this->deleteItem($query));
     }
  1. Department.php:285 – Pass $departmentId to deleteEntity:
-                $query->deleteEntity($item->getId());
+                $query->deleteEntity($item->getId(), $departmentId);
zmsdb/src/Zmsdb/Department.php (1)

129-136: Use fresh data for deletion guard.

Bypass cache when checking for non-empty scopes/clusters before delete.

-        $entity = $this->readEntity($departmentId, 1);
+        $entity = $this->readEntity($departmentId, 1, true);
♻️ Duplicate comments (8)
zmsdb/src/Zmsdb/Calldisplay.php (1)

88-89: Restore MIME type detection for backward compatibility.

The commented-out MIME detection code must be restored. Consumers of this method rely on $image['mime'] to set HTTP response headers (e.g., Content-Type). Without it, clients will fail to render images correctly or fall back to incorrect defaults.

This issue has been flagged multiple times in previous reviews and remains unaddressed.

Apply this diff to restore the MIME detection:

-        //$mime = pathinfo($image['name'], PATHINFO_EXTENSION);
-        //$image['mime'] = ($mime == 'jpg') ? 'jpeg' : $mime;
+        $mime = pathinfo($image['name'], PATHINFO_EXTENSION);
+        $image['mime'] = ($mime == 'jpg') ? 'jpeg' : $mime;
         return $image;
zmsdb/src/Zmsdb/Scope.php (5)

25-41: Init variable and guard writes with disableCache.

  • Initialize $scope before empty().
  • Don’t set cache when $disableCache is true.
-        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
-            $scope = App::$cache->get($cacheKey);
-        }
+        $scope = null;
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            $scope = App::$cache->get($cacheKey);
+        }
@@
-            if (App::$cache) {
+            if (!$disableCache && App::$cache) {
                 App::$cache->set($cacheKey, $scope);
             }

66-87: Guard all cache set() calls with disableCache and init $result.

Apply consistently across readByClusterId/readByProviderId/readByDepartmentId/readList.

-        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
-            $result = App::$cache->get($cacheKey);
-        }
+        $result = null;
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            $result = App::$cache->get($cacheKey);
+        }
@@
-            if (App::$cache && !($result instanceof \PDOStatement)) {
+            if (!$disableCache && App::$cache && !($result instanceof \PDOStatement)) {
                 App::$cache->set($cacheKey, $result);
             }

Repeat the if (!$disableCache && App::$cache) { App::$cache->set(...) } pattern at other set() sites shown above.

Also applies to: 119-133, 194-196, 260-273


676-688: Critical: wrong variable in provider check prevents invalidation.

You reference $this->provider->id instead of the entity’s provider. Fix condition.

-        if (isset($scope->provider) && isset($this->provider->id)) {
+        if (isset($scope->provider)) {
             if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-0')) {
                 App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-0');
             }
             if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-1')) {
                 App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-1');
             }
             if (App::$cache->has('scopeReadByProviderId-' . $scope->getProviderId() . '-2')) {
                 App::$cache->delete('scopeReadByProviderId-' . $scope->getProviderId() . '-2');
             }
         }

690-702: Critical: fix object property interpolation in cache keys.

Use concatenation or braces; current strings won’t interpolate $scope->id.

-            if (App::$cache->has("scope-$scope->id-0")) {
-                App::$cache->delete("scope-$scope->id-0");
-            }
-            if (App::$cache->has("scope-$scope->id-1")) {
-                App::$cache->delete("scope-$scope->id-1");
-            }
-            if (App::$cache->has("scope-$scope->id-2")) {
-                App::$cache->delete("scope-$scope->id-2");
-            }
+            foreach ([0,1,2] as $lvl) {
+                $key = 'scope-' . $scope->id . '-' . $lvl;
+                if (App::$cache->has($key)) {
+                    App::$cache->delete($key);
+                }
+            }

670-703: Broaden invalidation to cover list and cluster keys.

Stale "scopeReadList-" and "scopeReadByClusterId--" can persist after scope changes.

     public function removeCache($scope)
     {
         if (!App::$cache) {
             return;
         }
@@
+        // Invalidate list caches
+        foreach ([0,1,2] as $lvl) {
+            $listKey = 'scopeReadList-' . $lvl;
+            if (App::$cache->has($listKey)) {
+                App::$cache->delete($listKey);
+            }
+        }
+
+        // Invalidate cluster list caches when clusterId is available
+        $clusterId = null;
+        if (method_exists($scope, 'getClusterId')) {
+            $clusterId = $scope->getClusterId();
+        } elseif (isset($scope->clusterId)) {
+            $clusterId = $scope->clusterId;
+        }
+        if ($clusterId !== null) {
+            foreach ([0,1,2] as $lvl) {
+                $key = 'scopeReadByClusterId-' . (int)$clusterId . '-' . $lvl;
+                if (App::$cache->has($key)) {
+                    App::$cache->delete($key);
+                }
+            }
+        }
zmsdb/src/Zmsdb/Closure.php (1)

36-38: Respect disableCache on write to avoid unintended cache pollution.

You still write even when $disableCache === true. Align write with read guard.

-        if (App::$cache) {
-            App::$cache->set($cacheKey, $closureList);
-        }
+        if (!$disableCache && App::$cache) {
+            App::$cache->set($cacheKey, $closureList);
+        }
zmsdb/src/Zmsdb/Cluster.php (1)

48-50: Respect disableCache on write.

Skip set() when cache is bypassed.

-        if (App::$cache) {
+        if (!$disableCache && App::$cache) {
             App::$cache->set($cacheKey, $cluster);
         }
🧹 Nitpick comments (11)
zmsdb/src/Zmsdb/Calldisplay.php (3)

9-9: Verify if @SuppressWarnings is justified.

The @SuppressWarnings(CouplingBetweenObjects) annotation suppresses coupling warnings. While this class does coordinate multiple queries (Scope, Cluster, Organisation, Department), consider whether the coupling can be reduced through refactoring (e.g., introducing a service layer or repository pattern) rather than suppressing the warning.


29-33: Remove or implement commented exception handling.

The commented exception code violates clean code principles. If exception handling is needed, implement it properly. If the code is obsolete or the validation is performed elsewhere, remove the commented lines entirely.

As per coding guidelines: "Don't comment out code. Just remove."


43-47: Remove or implement commented exception handling.

Similar to lines 29-33, this commented exception code should either be implemented or removed entirely.

As per coding guidelines: "Don't comment out code. Just remove."

zmsdb/src/Zmsdb/Closure.php (2)

16-21: Optional: use has() for clarity and avoid truthiness pitfalls.

Minor readability win and consistent with other files.

-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }

12-12: Optional: avoid flag argument.

Split into readByScopeId() and readByScopeIdNoCache() or introduce a small CachePolicy enum. Improves readability.
Based on coding guidelines.

zmsdb/src/Zmsdb/DayOff.php (1)

31-41: Consistent cache get checks.

Use has()+get() symmetrically with other modules for consistency.

-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $data;
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            return App::$cache->get($cacheKey);
+        }

Also applies to: 62-72, 93-101

zmsdb/src/Zmsdb/Department.php (3)

24-31: Initialize $department before empty() for clarity.

Avoid relying on empty() on undefined variable.

-        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+        $department = null;
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
             $department = App::$cache->get($cacheKey);
         }

192-193: Ensure cache is actually invalidated on write/update.

removeCache($entity) may no-op if $entity->id is not set. Prefer invalidating by ID.

Option A (change calls):

-        $this->removeCache($entity);
+        $this->removeCache((object)['id' => $lastInsertId]);
-        $this->removeCache($entity);
+        $this->removeCache((object)['id' => $departmentId]);

Option B (change signature): make removeCache accept either Entity or int and branch on type.

Also applies to: 228-229


241-265: Also invalidate DayOff department cache after dayoff changes.

Call DayOff::removeCacheByDepartmentId() after updating department dayoffs to prevent stale lists.

         foreach ($dayoffList as $dayoff) {
             $query = new Query\DayOff(Query\Base::INSERT);
             $query->addValues(
                 [
                     'behoerdenid' => $departmentId,
                     'Feiertag' => $dayoff['name'],
                     'Datum' => (new \DateTimeImmutable('@' . $dayoff['date']))->format('Y-m-d')
                 ]
             );
             $this->writeItem($query);
         }
+        (new DayOff())->removeCacheByDepartmentId($departmentId);
zmsdb/src/Zmsdb/Cluster.php (1)

378-381: Ensure invalidation targets correct key.

removeCache($entity) may not delete anything if $entity->id is missing. Use the ID you have.

-        $this->removeCache($entity);
+        $this->removeCache((object)['id' => $lastInsertId]);
-        $this->removeCache($entity);
+        $this->removeCache((object)['id' => $clusterId]);

Also applies to: 402-405

zmsdb/src/Zmsdb/Scope.php (1)

176-201: Optional: consider caching resolveReferences=0 branch too.

You currently skip caching for the “simple match” path; if intentional, ignore. Otherwise, align with other list reads.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30cad00 and f709b4a.

📒 Files selected for processing (8)
  • zmsdb/src/Zmsdb/Calldisplay.php (1 hunks)
  • zmsdb/src/Zmsdb/Closure.php (4 hunks)
  • zmsdb/src/Zmsdb/Cluster.php (11 hunks)
  • zmsdb/src/Zmsdb/DayOff.php (6 hunks)
  • zmsdb/src/Zmsdb/Department.php (6 hunks)
  • zmsdb/src/Zmsdb/Link.php (4 hunks)
  • zmsdb/src/Zmsdb/Provider.php (5 hunks)
  • zmsdb/src/Zmsdb/Scope.php (14 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • zmsdb/src/Zmsdb/Provider.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Link.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Scope.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Link.php
  • zmsdb/src/Zmsdb/Cluster.php
  • zmsdb/src/Zmsdb/DayOff.php
  • zmsdb/src/Zmsdb/Closure.php
  • zmsdb/src/Zmsdb/Department.php
  • zmsdb/src/Zmsdb/Calldisplay.php
  • zmsdb/src/Zmsdb/Scope.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: call-php-unit-tests / zmsdb-test
🔇 Additional comments (7)
zmsdb/src/Zmsdb/Calldisplay.php (1)

93-112: LGTM!

The readContactData method has clean logic and properly handles both cluster and scope lists to build the contact name.

zmsdb/src/Zmsdb/Link.php (1)

5-5: LGTM - Import added for cache access.

The alias import is necessary for accessing the cache facility and aligns with the caching pattern used across other zmsdb components.

zmsdb/src/Zmsdb/Department.php (2)

50-66: Good propagation of disableCache to nested reads.


420-437: Critical: fix string interpolation of object property in cache keys.

Double-quoted "department-$department->id-*" does not interpolate as intended. Use concatenation or braces.

-        if (App::$cache->has("department-$department->id-0")) {
-            App::$cache->delete("department-$department->id-0");
-        }
-        if (App::$cache->has("department-$department->id-1")) {
-            App::$cache->delete("department-$department->id-1");
-        }
-        if (App::$cache->has("department-$department->id-2")) {
-            App::$cache->delete("department-$department->id-2");
-        }
+        foreach ([0,1,2] as $lvl) {
+            $key = 'department-' . $department->id . '-' . $lvl;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }

Likely an incorrect or invalid review comment.

zmsdb/src/Zmsdb/Cluster.php (2)

55-63: Correct propagation of disableCache to nested scope reads.


438-455: Critical: fix string interpolation in cache keys.

"cluster-$cluster->id-*" won’t interpolate the property. Use concatenation/brace syntax.

-        if (App::$cache->has("cluster-$cluster->id-0")) {
-            App::$cache->delete("cluster-$cluster->id-0");
-        }
-        if (App::$cache->has("cluster-$cluster->id-1")) {
-            App::$cache->delete("cluster-$cluster->id-1");
-        }
-        if (App::$cache->has("cluster-$cluster->id-2")) {
-            App::$cache->delete("cluster-$cluster->id-2");
-        }
+        foreach ([0,1,2] as $lvl) {
+            $key = 'cluster-' . $cluster->id . '-' . $lvl;
+            if (App::$cache->has($key)) {
+                App::$cache->delete($key);
+            }
+        }

Likely an incorrect or invalid review comment.

zmsdb/src/Zmsdb/Scope.php (1)

52-57: Good: disableCache propagated to DayOff and Closure reads.

Comment on lines +55 to +57
if (App::$cache) {
App::$cache->set($cacheKey, $dayOffList);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard cache writes with disableCache.

Writes should be skipped when $disableCache is true, mirroring read guards.

-        if (App::$cache) {
+        if (!$disableCache && App::$cache) {
             App::$cache->set($cacheKey, $dayOffList);
         }

Apply similarly at other set() sites in this file.

Also applies to: 86-88, 119-121

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/DayOff.php around lines 55-57 (and similarly at 86-88 and
119-121), the cache set() calls are unconditional; update each to skip writes
when $disableCache is true by wrapping the set call with a guard that checks
!$disableCache as well as App::$cache (e.g. if (!$disableCache && App::$cache) {
App::$cache->set($cacheKey, $dayOffList); }), applying the same pattern at the
other set() sites so cache writes mirror the existing read guards.

Comment on lines +97 to +105
$dayOffListCommon = $this->readCommon();

if (!$disableCache && App::$cache) {
$data = App::$cache->get($cacheKey);
if (!empty($data)) {
return $dayOffListCommon->addList($data);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: avoid mutating cached common list and honor disableCache.

  • readCommon() ignores $disableCache.
  • Returning $dayOffListCommon->addList(...) likely mutates the cached common list, polluting future reads.
-        $dayOffListCommon = $this->readCommon();
+        $dayOffListCommon = $this->readCommon($disableCache);

-        if (!$disableCache && App::$cache) {
-            $data = App::$cache->get($cacheKey);
-            if (!empty($data)) {
-                return $dayOffListCommon->addList($data);
-            }
-        }
+        if (!$disableCache && App::$cache && App::$cache->has($cacheKey)) {
+            $data = App::$cache->get($cacheKey);
+            $combined = new Collection();
+            $combined->addList($dayOffListCommon);
+            $combined->addList($data);
+            return $combined;
+        }

-        return $dayOffListCommon->addList($dayOffList);
+        $combined = new Collection();
+        $combined->addList($dayOffListCommon);
+        $combined->addList($dayOffList);
+        return $combined;

Also applies to: 123-124

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/DayOff.php around lines 97-105 (and similarly at 123-124),
readCommon() is called without honoring $disableCache and the code returns
$dayOffListCommon->addList($data) which mutates the common list and pollutes
cached state; change the flow so that when $disableCache is true you bypass
App::$cache entirely, and when reading from cache you do not call addList on the
cached object directly — instead create a new DayOffList (or deep-clone
$dayOffListCommon) and call addList on that new instance (or merge immutably)
before returning; ensure readCommon accepts/propagates the $disableCache flag if
needed or call a non-cached variant, and never mutate the cached shared object.

Comment on lines +241 to +250
public function removeCache()
{
if (!App::$cache) {
return;
}

if (App::$cache->has("dayOffs")) {
App::$cache->delete("dayOffs");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Extend invalidation: add per-department and per-scope removals.

Only deleting "dayOffs" leaves "dayOffsReadOnlyByDepartmentId-" and "dayOffsByScope-" stale after writes/deletes.

     public function removeCache()
     {
         if (!App::$cache) {
             return;
         }

         if (App::$cache->has("dayOffs")) {
             App::$cache->delete("dayOffs");
         }
+
+        // Optional helpers (call from write/delete paths when IDs are known)
+        // Remove per-department cache
+        // Usage: $this->removeCacheByDepartmentId($departmentId);
+    }
+
+    public function removeCacheByDepartmentId($departmentId): void
+    {
+        if (!App::$cache) { return; }
+        $key = 'dayOffsReadOnlyByDepartmentId-' . (int)$departmentId;
+        if (App::$cache->has($key)) {
+            App::$cache->delete($key);
+        }
+    }
+
+    public function removeCacheByScopeId($scopeId): void
+    {
+        if (!App::$cache) { return; }
+        $key = 'dayOffsByScope-' . (int)$scopeId;
+        if (App::$cache->has($key)) {
+            App::$cache->delete($key);
+        }
     }

Follow-up: call removeCacheByDepartmentId() in Department::writeDepartmentDayoffs() after modifications. Based on coding guidelines.

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/DayOff.php around lines 241-250, extend removeCache() so it
not only deletes the "dayOffs" key but also removes per-department and per-scope
entries to avoid stale cache: detect and delete all keys matching
"dayOffsReadOnlyByDepartmentId-*" and "dayOffsByScope-*" (use the cache
provider's key listing / pattern deletion API or iterate keys and delete
matches), and add a new public removeCacheByDepartmentId($departmentId) helper
that deletes "dayOffsReadOnlyByDepartmentId-{$departmentId}" and the global
"dayOffs" plus any scope keys for that department; then, per the follow-up, call
removeCacheByDepartmentId($deptId) from Department::writeDepartmentDayoffs()
immediately after making modifications.

Comment on lines +37 to +39
if (App::$cache) {
App::$cache->set($cacheKey, $department);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Respect disableCache on write.

Skip set() when bypassing cache.

-            if (App::$cache) {
+            if (!$disableCache && App::$cache) {
                 App::$cache->set($cacheKey, $department);
             }
🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Department.php around lines 37 to 39, the current code always
calls App::$cache->set($cacheKey, $department) when App::$cache exists; update
it to respect the cache bypass flag by only calling set when caching is enabled
(e.g., check that App::$disableCache is not true): change the condition to
require App::$cache and that cache is not disabled, and leave behavior unchanged
otherwise.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
zmsdb/tests/Zmsdb/DepartmentTest.php (1)

15-15: LGTM! Consider adding test coverage for withDayOff=false.

The addition of the third parameter true correctly aligns with the test's assertions that verify dayoff data (lines 21-22). The change properly reflects the updated method signature.

For more comprehensive test coverage, consider adding a test case that verifies the behavior when withDayOff=false is passed to ensure the parameter works as expected in both scenarios. For example:

public function testReadWithoutDayOff()
{
    $query = new Query();
    $input = $this->getTestEntity();
    $entity = $query->writeEntity($input, 75);
    $entity = $query->readEntity($entity->id, 2, false);
    $this->assertEquals(0, count($entity->dayoff));
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f709b4a and d217245.

📒 Files selected for processing (1)
  • zmsdb/tests/Zmsdb/DepartmentTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/tests/Zmsdb/DepartmentTest.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/tests/Zmsdb/DepartmentTest.php

Fabinatix97
Fabinatix97 previously approved these changes Oct 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d217245 and 599a86e.

📒 Files selected for processing (1)
  • zmsdb/src/Zmsdb/Connection/Select.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*

⚙️ CodeRabbit configuration file

**/*: Apply the following Clean Code guidelines to all files, as summarized by wojteklu's Clean Code gist:

Code is clean if it can be understood easily – by everyone on the team. Clean code can be read and enhanced by a developer other than its original author. With understandability comes readability, changeability, extensibility and maintainability.

General rules

  1. Follow standard conventions.
  2. Keep it simple stupid. Simpler is always better. Reduce complexity as much as possible.
  3. Boy scout rule. Leave the campground cleaner than you found it.
  4. Always find root cause. Always look for the root cause of a problem.

Design rules

  1. Keep configurable data at high levels.
  2. Prefer polymorphism to if/else or switch/case.
  3. Separate multi-threading code.
  4. Prevent over-configurability.
  5. Use dependency injection.
  6. Follow Law of Demeter. A class should know only its direct dependencies.

Understandability tips

  1. Be consistent. If you do something a certain way, do all similar things in the same way.
  2. Use explanatory variables.
  3. Encapsulate boundary conditions. Boundary conditions are hard to keep track of. Put the processing for them in one place.
  4. Prefer dedicated value objects to primitive type.
  5. Avoid logical dependency. Don't write methods which works correctly depending on something else in the same class.
  6. Avoid negative conditionals.

Names rules

  1. Choose descriptive and unambiguous names.
  2. Make meaningful distinction.
  3. Use pronounceable names.
  4. Use searchable names.
  5. Replace magic numbers with named constants.
  6. Avoid encodings. Don't append prefixes or type information.

Functions rules

  1. Small.
  2. Do one thing.
  3. Use descriptive names.
  4. Prefer fewer arguments.
  5. Have no side effects.
  6. Don't use flag arguments. Split method into several independent methods that can b...

Files:

  • zmsdb/src/Zmsdb/Connection/Select.php
**/*.php

⚙️ CodeRabbit configuration file

**/*.php: Flag any usage of error_log() as it should be replaced with proper Monolog logging mechanisms provided by zmsslim which should be setup in the App/Application of each module:

  1. For error handling: Use the proper Monolog logging framework with error levels
  2. For application info logs: Use the proper Monolog logging framework with info levels
  3. For debugging: Use a dedicated debug logger or remove debug statements
  4. For CLI output: Use a CLI output handler or symfony/console
  5. The application log levels are as follows DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, and EMERGENCY

Example replacement:

// Instead of:
error_log("Import failed - " . $e->getMessage());

// Use:
$log->error("Import failed", ['error' => $e->getMessage()]);

Flag specific logging violations:

  1. error_log(), var_dump(), print_r(), die(), exit() usage (except proper error logging in catch blocks)
  2. Any debug output that should use proper Monolog logging
  3. Debug constants like DEBUG = true
  4. Debug logging that should be removed in production
  5. Commented debug code that should be cleaned up

Example replacements:

// Instead of:
error_log("Error occurred");
var_dump($data);
die('debug point');

// Use:
$log->error("Error occurred", ['context' => 'processing']);
$log->debug('Data dump', ['data' => $data]);
// Remove die() statements entirely

Exception handling should use proper logging:

// Instead of:
try {
    $result = $this->process();
} catch (Exception $e) {
    error_log("Processing failed: " . $e->getMessage());
}

// Use:
try {
    $result = $this->process();
} catch (Exception $e) {
    $log->error("Processing failed", ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]);
}
```...

Files:

  • zmsdb/src/Zmsdb/Connection/Select.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: call-php-unit-tests / zmsapi-test
  • GitHub Check: call-php-unit-tests / zmsdb-test

Comment on lines 119 to 121
$pdoOptions = array_merge([
Pdo::ATTR_PERSISTENT => true
], self::$pdoOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Make persistent connections configurable, not hardcoded.

Hardcoding ATTR_PERSISTENT => true violates the principle of keeping configurable data at high levels. Persistent connections can have significant operational impact and should be controllable via configuration or environment variables.

Additionally, persistent connections will cause session-level settings (lines 123, 126-127, 164, 170, 219-220, 225, 234-238) to persist across requests, which may lead to unexpected behavior if connections are reused in ways that weren't anticipated.

As per coding guidelines.

Apply this diff to make it configurable:

+    /**
+     * @var Bool $enablePersistentConnections
+     */
+    public static $enablePersistentConnections = false;
+
     protected static function createPdoConnection($dataSourceName)
     {
         try {
-            $pdoOptions = array_merge([
-                Pdo::ATTR_PERSISTENT => true
-                ], self::$pdoOptions);
+            $pdoOptions = self::$pdoOptions;
+            if (self::$enablePersistentConnections) {
+                $pdoOptions[Pdo::ATTR_PERSISTENT] = true;
+            }
             $pdo = new Pdo($dataSourceName, self::$username, self::$password, $pdoOptions);

Then set it during application initialization:

// In application bootstrap
\BO\Zmsdb\Connection\Select::$enablePersistentConnections = 
    (getenv('DB_PERSISTENT_CONNECTIONS') === 'true');

Run the following script to check if persistent connections are already configurable elsewhere or if there are patterns for this in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for other persistent connection configuration patterns
rg -n -C3 'ATTR_PERSISTENT|enablePersistent|persistent.*connection' --type=php

# Search for how other static properties in Select are configured
rg -n -C3 'Select::\$' --type=php -g '!**/Connection/Select.php'

Length of output: 14501


Hardcoded persistent connections must be made configurable.

The verification confirms this is the only occurrence of ATTR_PERSISTENT => true in the codebase, and there is no existing configuration mechanism for it. The codebase establishes a clear pattern for externalizing connection settings through static properties configured during bootstrap (see zmsdb/config.example.php lines 21–35, and zmsdldb/bootstrap.php lines 22–30, where $enableProfiling, $readSourceName, $writeSourceName, and $pdoOptions are all externalized). Hardcoding persistent connections violates this design and the principle of keeping configurable data at high levels.

Additionally, persistent connections cause session-level settings (lines 123, 126–127, 164, 170, 219–220, 225, 234–238) to persist across requests, creating operational risks that should be controllable.

Apply the suggested diff to make it configurable via the static property pattern already in use throughout the codebase, then set it during application bootstrap using an environment variable.

🤖 Prompt for AI Agents
In zmsdb/src/Zmsdb/Connection/Select.php around lines 119 to 121, remove the
hardcoded Pdo::ATTR_PERSISTENT => true and make the persistent option
configurable by reading it from the existing static pdo options pattern
(self::$pdoOptions) so that the default merge does not force persistence; ensure
callers/bootstrap set self::$pdoOptions['ATTR_PERSISTENT'] (or the correct Pdo
constant key) from an environment variable during application bootstrap (e.g.,
read env var like ZMSDB_PDO_PERSISTENT and cast to boolean) so persistent
connections are opt-in and controlled at startup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants