Lines 98.09% 309 / 315
Methods 94.73% 36 / 38
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 readEntity 100.00% 18 / 18 100.00% 1 / 1 8
 readResolvedReferences 100.00% 2 / 2 100.00% 1 / 1 1
 readEntityWithOpenedScopeStatus 100.00% 4 / 4 100.00% 1 / 1 2
 readList 100.00% 12 / 12 100.00% 1 / 1 4
 readByScopeId 100.00% 10 / 10 100.00% 1 / 1 2
 readByDepartmentId 100.00% 13 / 13 100.00% 1 / 1 5
 readQueueList 100.00% 4 / 4 100.00% 1 / 1 1
 readOpenedScopeList 100.00% 9 / 9 100.00% 1 / 1 5
 readEnabledScopeList 100.00% 5 / 5 100.00% 1 / 1 3
 readScopeWithShortestWaitingTime 100.00% 16 / 16 100.00% 1 / 1 7
 readWithScopeWorkstationCount 100.00% 10 / 10 100.00% 1 / 1 4
 writeImageData 93.33% 14 / 15 0.00% 0 / 1 4.00
 readImageData 100.00% 10 / 10 100.00% 1 / 1 2
 deleteImage 100.00% 8 / 8 100.00% 1 / 1 1
 deleteEntity 100.00% 11 / 11 100.00% 1 / 1 2
 writeEntity 100.00% 9 / 9 100.00% 1 / 1 2
 updateEntity 100.00% 9 / 9 100.00% 1 / 1 2
 writeAssignedScopes 100.00% 15 / 15 100.00% 1 / 1 3
 removeCache 100.00% 18 / 18 100.00% 1 / 1 9
 [BO\Zmsbackend\Base] __construct 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] init 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] getWriter 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] getReader 100.00% 5 / 5 100.00% 1 / 1 3
 [BO\Zmsbackend\Base] fetchPreparedStatement 100.00% 9 / 9 100.00% 1 / 1 3
 [BO\Zmsbackend\Base] startExecute 100.00% 5 / 5 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] pdoExceptionHandler 100.00% 13 / 13 100.00% 1 / 1 4
 [BO\Zmsbackend\Base] fetchStatement 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchOne 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] fetchRow 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchValue 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchAll 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchHandle 100.00% 5 / 5 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchList 100.00% 8 / 8 100.00% 1 / 1 2
 [BO\Zmsbackend\Base] writeItem 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] deleteItem 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] perform 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] fetchAffected 100.00% 8 / 8 100.00% 1 / 1 1
 [BO\Zmsbackend\Base] hashStringValue 50.00% 5 / 10 0.00% 0 / 1 8.12
15class Cluster extends \BO\Zmsbackend\Base
16{
17    /**
18     * @param false|int|string $itemId
19     *
20     */
21    public function readEntity(string|int|false $itemId, int $resolveReferences = 0, bool $disableCache = false)
22    {
23        $cacheKey = "cluster-$itemId-$resolveReferences";
24
25        $cluster = null;
26        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
27            $cluster = \App::$cache->get($cacheKey);
28        }
29
30        if (empty($cluster)) {
31            $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::SELECT);
32            $query
33                ->addEntityMapping()
34                ->addResolvedReferences($resolveReferences)
35                ->addConditionClusterId($itemId);
36            $cluster = $this->fetchOne($query, new Entity());
37            if (! $cluster->hasId()) {
38                return null;
39            }
40
41            if (\App::$cache) {
42                \App::$cache->set($cacheKey, $cluster);
43                if (\App::$log) {
44                    \App::$log->info('Cluster cache set', ['cache_key' => $cacheKey]);
45                }
46            }
47        }
48
49        return $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
50    }
51
52    /**
53     * @return \BO\Zmsentities\Schema\Entity
54     */
55    #[\Override]
56    public function readResolvedReferences(
57        \BO\Zmsentities\Schema\Entity $entity,
58        int $resolveReferences,
59        bool $disableCache = false
60    ) {
61        $entity['scopes'] = (new \BO\Zmsbackend\Scope\Service\Scope())->readByClusterId($entity->id, $resolveReferences, $disableCache);
62
63        return $entity;
64    }
65
66    public function readEntityWithOpenedScopeStatus($itemId, \DateTimeInterface $now, int $resolveReferences = 0)
67    {
68        $entity = $this->readEntity($itemId, $resolveReferences);
69        foreach ($entity->scopes as $scope) {
70            $scope->setStatusAvailability('isOpened', (new \BO\Zmsbackend\Scope\Service\Scope())->readIsOpened($scope->getId(), $now));
71        }
72        return $entity;
73    }
74
75    public function readList(int $resolveReferences = 0): Collection
76    {
77        $clusterList = new Collection();
78        $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::SELECT);
79        $query
80            ->addResolvedReferences($resolveReferences)
81            ->addEntityMapping();
82        $result = $this->fetchList($query, new Entity());
83        if (count($result)) {
84            foreach ($result as $entity) {
85                if ($entity instanceof Entity) {
86                    $entity = $this->readResolvedReferences($entity, $resolveReferences);
87                    $clusterList->addEntity($entity);
88                }
89            }
90        }
91        return $clusterList;
92    }
93
94    public function readByScopeId($scopeId, int $resolveReferences = 0)
95    {
96        $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::SELECT);
97        $query
98            ->addEntityMapping()
99            ->addResolvedReferences($resolveReferences)
100            ->addConditionScopeId($scopeId);
101        $entity = $this->fetchOne($query, new Entity());
102        if (! $entity->hasId()) {
103            return null;
104        }
105        $entity = $this->readResolvedReferences($entity, $resolveReferences);
106        return $entity;
107    }
108
109    public function readByDepartmentId($departmentId, int $resolveReferences = 0, bool $disableCache = false): Collection
110    {
111        $clusterList = new Collection();
112        $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::SELECT);
113        $query
114            ->addEntityMapping()
115            ->addResolvedReferences($resolveReferences)
116            ->addConditionDepartmentId($departmentId);
117        $result = $this->fetchList($query, new Entity());
118        if (count($result)) {
119            foreach ($result as $entity) {
120                if ($entity instanceof Entity && !$clusterList->hasEntity($entity->id)) {
121                    $entity = $this->readResolvedReferences($entity, $resolveReferences, $disableCache);
122                    $clusterList->addEntity($entity);
123                }
124            }
125        }
126        return $clusterList;
127    }
128
129    /**
130     * @param string[] $withEntities
131     *
132     */
133    public function readQueueList(
134        $clusterId,
135        \DateTimeInterface $dateTime,
136        int $resolveReferences = 0,
137        array $withEntities = []
138    ) {
139        $cluster = $this->readEntity($clusterId, 1);
140
141        return (new \BO\Zmsbackend\Scope\Service\Scope())
142            ->readScopesQueueListWithWaitingTime($cluster->scopes, $dateTime, $resolveReferences, $withEntities)
143            ->withSortedWaitingTime();
144    }
145
146    public function readOpenedScopeList($clusterId, \DateTimeInterface $dateTime): \BO\Zmsentities\Collection\ScopeList
147    {
148        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
149        $cluster = $this->readEntity($clusterId, 1);
150        if ($cluster && $cluster->toProperty()->scopes->get()) {
151            foreach ($cluster->scopes as $scope) {
152                $availabilityList = (new \BO\Zmsbackend\Availability\Service\Availability())->readOpeningHoursListByDate($scope['id'], $dateTime, 2);
153                if ($availabilityList->isOpened($dateTime)) {
154                    $scope->setStatusAvailability('isOpened', true);
155                    $scopeList->addEntity($scope);
156                }
157            }
158        }
159        return $scopeList;
160    }
161
162    public function readEnabledScopeList($clusterId, \DateTimeInterface $dateTime): \BO\Zmsentities\Collection\ScopeList
163    {
164        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
165        foreach ($this->readOpenedScopeList($clusterId, $dateTime) as $scope) {
166            if ((new \BO\Zmsbackend\Scope\Service\Scope())->readIsGivenNumberInContingent($scope['id'])) {
167                $scopeList->addEntity($scope);
168            }
169        }
170        return $scopeList;
171    }
172
173    public function readScopeWithShortestWaitingTime($clusterId, \DateTimeInterface $dateTime)
174    {
175        $scopeList = $this->readOpenedScopeList($clusterId, $dateTime)->getArrayCopy();
176        $nextScope = array_shift($scopeList);
177        $preferedScope = null;
178        $preferedWaitingTime = 0;
179        while ($nextScope) {
180            $scope = (new \BO\Zmsbackend\Scope\Service\Scope())->readWithWorkstationCount($nextScope->id, $dateTime);
181            $queueList = (new \BO\Zmsbackend\Scope\Service\Scope())->readQueueListWithWaitingTime($scope, $dateTime);
182            $data = $scope->getWaitingTimeFromQueueList($queueList, $dateTime);
183            if (
184                $scope->getCalculatedWorkstationCount() > 0 &&
185                $data &&
186                ($data['waitingTimeEstimate'] <= $preferedWaitingTime || 0 == $preferedWaitingTime)
187            ) {
188                $preferedWaitingTime = $data['waitingTimeEstimate'];
189                $preferedScope = $scope;
190            }
191            $nextScope = array_shift($scopeList);
192        }
193        if (! $preferedScope) {
194            throw new \BO\Zmsbackend\Cluster\Exception\ScopesWithoutWorkstationCount();
195        }
196        return $preferedScope;
197    }
198
199    public function readWithScopeWorkstationCount($clusterId, $dateTime, int $resolveReferences = 0)
200    {
201        $scopeQuery = new \BO\Zmsbackend\Scope\Service\Scope();
202        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
203        $cluster = $this->readEntity($clusterId, $resolveReferences);
204        if ($cluster->toProperty()->scopes->get()) {
205            foreach ($cluster->scopes as $scope) {
206                $entity = $scopeQuery->readWithWorkstationCount($scope->id, $dateTime, $resolveReferences = 0);
207                if ($entity) {
208                    $scopeList->addEntity($entity);
209                }
210            }
211        }
212        $cluster->scopes = $scopeList;
213        return $cluster;
214    }
215
216    public function writeImageData($clusterId, \BO\Zmsentities\Mimepart $entity): \BO\Zmsentities\Mimepart
217    {
218        if ($entity->mime && $entity->content) {
219            $this->deleteImage($clusterId);
220            $extension = $entity->getExtension();
221            if ($extension == 'jpeg') {
222                $extension = 'jpg'; //compatibility ZMS1
223            }
224            $imageName = 'c_' . $clusterId . '_bild.' . $extension;
225            $this->perform(
226                (new \BO\Zmsbackend\Query\Scope(\BO\Zmsbackend\Query\Base::REPLACE))->getQueryWriteImageData(),
227                array(
228                'imagename' => $imageName,
229                'imagedata' => $entity->content
230                )
231            );
232        }
233        $entity->id = $clusterId;
234        return $entity;
235    }
236
237    public function readImageData($clusterId): \BO\Zmsentities\Mimepart
238    {
239        $imageName = 'c_' . $clusterId . '_bild';
240        $imageData = new \BO\Zmsentities\Mimepart();
241        $fileData = $this->getReader()->fetchAll(
242            (new \BO\Zmsbackend\Query\Scope(\BO\Zmsbackend\Query\Base::SELECT))->getQueryReadImageData(),
243            ['imagename' => "$imageName%"]
244        );
245        if ($fileData) {
246            $imageData->content = $fileData[0]['imagecontent'];
247            $imageData->mime = pathinfo($fileData[0]['imagename'])['extension'];
248        }
249        return $imageData;
250    }
251
252    public function deleteImage($clusterId)
253    {
254        $imageName = 'c_' . $clusterId . '_bild';
255        $result = $this->perform(
256            (new \BO\Zmsbackend\Query\Scope(\BO\Zmsbackend\Query\Base::DELETE))->getQueryDeleteImage(),
257            array(
258            'imagename' => "$imageName%"
259            )
260        );
261        return $result;
262    }
263
264    public function deleteEntity($itemId)
265    {
266        $result = false;
267        $cluster = $this->readEntity($itemId);
268        $query =  new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::DELETE);
269        $query->addConditionClusterId($itemId);
270        if ($this->deleteItem($query)) {
271            $result = $this->perform(
272                (new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::DELETE))->getQueryDeleteAssignedScopes(),
273                ['clusterId' => $itemId]
274            );
275        }
276
277        $this->removeCache($cluster);
278
279        return $result;
280    }
281
282    public function writeEntity(\BO\Zmsentities\Cluster $entity)
283    {
284        $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::INSERT);
285        $values = $query->reverseEntityMapping($entity);
286        $query->addValues($values);
287        $this->writeItem($query);
288        $lastInsertId = $this->getWriter()->lastInsertId();
289        if ($entity->toProperty()->scopes->isAvailable()) {
290            $this->writeAssignedScopes($lastInsertId, $entity->scopes);
291        }
292
293        $this->removeCache($entity);
294
295        return $this->readEntity($lastInsertId, 1, true);
296    }
297
298    public function updateEntity($clusterId, \BO\Zmsentities\Cluster $entity)
299    {
300        $query = new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::UPDATE);
301        $query->addConditionClusterId($clusterId);
302        $values = $query->reverseEntityMapping($entity);
303        $query->addValues($values);
304        $this->writeItem($query);
305        if ($entity->toProperty()->scopes->isAvailable()) {
306            $this->writeAssignedScopes($clusterId, $entity->scopes);
307        }
308
309        $this->removeCache($entity);
310
311        return $this->readEntity($clusterId, 1, true);
312    }
313
314    protected function writeAssignedScopes(string|false $clusterId, $scopeList): void
315    {
316        $cluster = $this->readEntity($clusterId);
317        $this->perform(
318            (new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::DELETE))->getQueryDeleteAssignedScopes(),
319            ['clusterId' => $clusterId]
320        );
321        foreach ($scopeList as $scope) {
322            if (0 < $scope['id']) {
323                $this->perform(
324                    (new \BO\Zmsbackend\Cluster\Repository\Cluster(\BO\Zmsbackend\Query\Base::REPLACE))->getQueryWriteAssignedScopes(),
325                    array(
326                    'clusterId' => $clusterId,
327                    'scopeId' => $scope['id']
328                    )
329                );
330            }
331        }
332
333        $this->removeCache($cluster);
334    }
335
336    /**
337     * @return void
338     */
339    public function removeCache(Entity $cluster)
340    {
341        if (!\App::$cache || !isset($cluster->id)) {
342            return;
343        }
344
345        $invalidatedKeys = [];
346
347        // Invalidate cluster entity cache for all resolveReferences levels (0, 1, 2)
348        for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
349            $key = "cluster-{$cluster->id}-{$resolveReferences}";
350            if (\App::$cache->has($key)) {
351                \App::$cache->delete($key);
352                $invalidatedKeys[] = $key;
353            }
354        }
355
356        // Invalidate scopeReadByClusterId cache for all resolveReferences levels (0, 1, 2)
357        for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
358            $key = "scopeReadByClusterId-{$cluster->id}-{$resolveReferences}";
359            if (\App::$cache->has($key)) {
360                \App::$cache->delete($key);
361                $invalidatedKeys[] = $key;
362            }
363        }
364
365        if (!empty($invalidatedKeys) && \App::$log) {
366            \App::$log->info('Cluster cache invalidated', [
367                'cluster_id' => $cluster->id,
368                'invalidated_keys' => $invalidatedKeys
369            ]);
370        }
371    }
372}

Inherited from BO\Zmsbackend\Base

38    public function __construct(?Connection\Pdo $writeConnection = null, ?Connection\Pdo $readConnection = null)
39    {
40        $this->writeDb = $writeConnection;
41        $this->readDb = $readConnection;
42    }
44    public static function init(?Connection\Pdo $writeConnection = null, ?Connection\Pdo $readConnection = null): static
45    {
46        $instance = new static($writeConnection, $readConnection);
47        return $instance;
48    }
50    public function getWriter(): Connection\Pdo
51    {
52        if (null === $this->writeDb) {
53            $this->writeDb = Connection\Select::getWriteConnection();
54            $this->readDb = $this->writeDb;
55        }
56        return $this->writeDb;
57    }
59    public function getReader(): Connection\Pdo
60    {
61        if (null !== $this->writeDb) {
62            // if readDB gets a reset, still use writeDB
63            return $this->writeDb;
64        }
65        if (null === $this->readDb) {
66            $this->readDb = Connection\Select::getReadConnection();
67        }
68        return $this->readDb;
69    }
71    public function fetchPreparedStatement(Query\Base|string $query)
72    {
73        $sql = "$query";
74        $reader = $this->getReader();
75        if (spl_object_hash($reader) != static::$preparedConnectionId) {
76            // do not use prepared statements on a different connection
77            static::$preparedCache = [];
78            static::$preparedConnectionId = spl_object_hash($reader);
79        }
80        if (!isset(static::$preparedCache[$sql])) {
81            $prepared = $this->getReader()->prepare($sql);
82            static::$preparedCache[$sql] = $prepared;
83        }
84        return static::$preparedCache[$sql];
85    }
87    public function startExecute($statement, array $parameters)
88    {
89        $statement = static::pdoExceptionHandler(function () use ($statement, $parameters) {
90            $statement->execute($parameters);
91            return $statement;
92        });
93        return $statement;
94    }
96    protected static function pdoExceptionHandler(\Closure $pdoFunction, $parameters = [])
97    {
98        try {
99            $statement = $pdoFunction($parameters);
100        } catch (\PDOException $pdoException) {
101            if (stripos($pdoException->getMessage(), 'Lock wait timeout') !== false) {
102                throw new \BO\Zmsbackend\Exception\Pdo\LockTimeout();
103            }
104            //@codeCoverageIgnoreStart
105            if (stripos($pdoException->getMessage(), 'Deadlock found') !== false) {
106                throw new \BO\Zmsbackend\Exception\Pdo\DeadLockFound();
107            }
108            //@codeCoverageIgnoreEnd
109            $message = "SQL: "
110                . " Err: "
111                . $pdoException->getMessage()
112                //. " || Statement: "
113                //.$statement->queryString
114                //." || Parameters=". var_export($parameters, true)
115                ;
116            throw new \BO\Zmsbackend\Exception\Pdo\PDOFailed($message, 0, $pdoException);
117        }
118        return $statement;
119    }
121    public function fetchStatement(Query\Base $query)
122    {
123        $parameters = $query->getParameters();
124        $statement = $this->startExecute($this->fetchPreparedStatement($query), $parameters);
125        return $statement;
126    }
133    public function fetchOne(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity)
134    {
135        $statement = $this->fetchStatement($query);
136        $data = $statement->fetch(\PDO::FETCH_ASSOC);
137        if ($data) {
138            $entity->exchangeArray($query->postProcessJoins($data));
139            $entity->setResolveLevel($query->getResolveLevel());
140        }
141        $statement->closeCursor();
142        return $entity;
143    }
145    public function fetchRow(Query\Base|string $query, array|null $parameters = null)
146    {
147        return static::pdoExceptionHandler(function () use ($query, $parameters) {
148            $prepared = $this->fetchPreparedStatement($query);
149            $prepared->execute($parameters);
150            $row = $prepared->fetch(\PDO::FETCH_ASSOC);
151            $prepared->closeCursor();
152            return $row;
153        });
154    }
156    public function fetchValue(Query\Base|string $query, array|null $parameters = null)
157    {
158        return static::pdoExceptionHandler(function () use ($query, $parameters) {
159            $prepared = $this->fetchPreparedStatement($query);
160            $prepared->execute($parameters);
161            $value = $prepared->fetchColumn();
162            $prepared->closeCursor();
163            return $value;
164        });
165    }
167    public function fetchAll(Query\Base|string $query, array|null $parameters = null)
168    {
169        return static::pdoExceptionHandler(function () use ($query, $parameters) {
170            $prepared = $this->fetchPreparedStatement($query);
171            $prepared->execute($parameters);
172            $list = $prepared->fetchAll(\PDO::FETCH_ASSOC);
173            $prepared->closeCursor();
174            return $list;
175        });
176    }
178    public function fetchHandle(Query\Base|string $query, array|null $parameters = null)
179    {
180        return static::pdoExceptionHandler(function () use ($query, $parameters) {
181            $prepared = $this->fetchPreparedStatement($query);
182            $prepared->execute($parameters);
183            return $prepared;
184        });
185    }
187    public function fetchList(Query\Base $query, \BO\Zmsentities\Schema\Entity $entity, array|\ArrayAccess $resultList = [])
188    {
189        $statement = $this->fetchStatement($query);
190        while ($data = $statement->fetch(\PDO::FETCH_ASSOC)) {
191            $dataEntity = clone $entity;
192            $dataEntity->exchangeArray($query->postProcessJoins($data));
193            $dataEntity->setResolveLevel($query->getResolveLevel());
194            $resultList[] = $dataEntity;
195        }
196        $statement->closeCursor();
197        return $resultList;
198    }
206    public function writeItem(Query\Base $query)
207    {
208        return static::pdoExceptionHandler(function () use ($query) {
209            $this->getWriter(); //Switch to writer for write/delete
210            $statement = $this->fetchPreparedStatement($query);
211            $status = $statement->execute($query->getParameters());
212            $statement->closeCursor();
213            return $status;
214        });
215    }
221    public function deleteItem(Query\Base $query)
222    {
223        return $this->writeItem($query);
224    }
226    public function perform(string $sql, array|null $parameters = null)
227    {
228        return static::pdoExceptionHandler(function () use ($sql, $parameters) {
229            $this->getWriter(); //Switch to writer for perform
230            $prepared = $this->fetchPreparedStatement($sql);
231            $status = $prepared->execute($parameters);
232            $prepared->closeCursor();
233            return $status;
234        });
235    }
237    public function fetchAffected(string $sql, array|null $parameters = null)
238    {
239        return static::pdoExceptionHandler(function () use ($sql, $parameters) {
240            $this->getWriter(); //Switch to writer for perform
241            $prepared = $this->fetchPreparedStatement($sql);
242            $prepared->execute($parameters);
243            $count = $prepared->rowCount();
244            $prepared->closeCursor();
245            return $count;
246        });
247    }
268    public function hashStringValue(string $value, ?callable $callable = null): string
269    {
270        if ($callable === null) {
271            $callable = 'sha1';
272        }
273
274        if (is_callable($callable)) {
275            $hash = $callable($value);
276
277            if (is_string($callable)) {
278                return $callable . ':' . $hash;
279            }
280            if ($callable instanceof \Closure) {
281                return 'closure:' . $hash;
282            }
283            // else
284            return 'custom:' . $hash;
285        }
286
287        throw new \InvalidArgumentException('hashStringValue() should be called with a callable as second parameter.');
288    }