GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (35)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/statemachine/persistence/Redis.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace izzum\statemachine\persistence;
3
use izzum\statemachine\loader\Loader;
4
use izzum\statemachine\StateMachine;
5
use izzum\statemachine\Context;
6
use izzum\statemachine\loader\LoaderArray;
7
use izzum\statemachine\loader\LoaderData;
8
use izzum\statemachine\Exception;
9
use izzum\statemachine\Identifier;
10
use izzum\statemachine\Transition;
11
use izzum\statemachine\loader\JSON;
12
/**
13
 * Redis is an open source advanced key-value (nosql database) cache and store using
14
 * datastructures.
15
 * 
16
 * Since redis has no schemas (as a nosql db), it should store data based on the way you want to retrieve data.
17
 * There will be multiple views on the data which should make sense for a lot of use cases (counters, 
18
 * sets, sorted sets, lists, hashes etc) to retrieve the data.
19
 * All keys that izzum uses in redis can be found in this classes' constants with a 'KEY_' prefix.
20
 * 
21
 * This class uses the php redis module and your php build should be setup with this module loaded.
22
 * 
23
 * An instance uses a redis key prefix of 'izzum:' by default. but this can be set to whatever you like.
24
 *
25
 * .
26
 * You can even set the prefix key to something else temporarily for loading the configuration data and
27
 * then set it to another prefix for writing state data. This allows you to store multiple machine 
28
 * configurations under different prefixes so you can load different machines from different places, 
29
 * which facilitates multiple dev teams working on configuration of different machines without them
30
 * overwriting other teams' definitions.
31
 * 
32
 * The configuration of statemachines is a JSON string. The specification of the JSON string can 
33
 * be found in izzum\statemachine\loader\JSON::getJSONSchema. see asset/json/json.schema
34
 * 
35
 * Internally, this class uses the JSON loader to load the configuration. It can be set up to 
36
 * store multiple configurations under multiple keys.
37
 * 
38
 * You can use the normal redis connection settings to connect to redis.
39
 *
40
 * @link http://redis.io
41
 * @link https://github.com/nicolasff/phpredis a php module for redis you'll need to use this class.
42
 * you need to install the php module and make it available to php:
43
 * - debian/ubuntu: via the apt package manager: apt-get install php5-redis
44
 * - osx: use homebrew
45
 *      - install homebrew php https://github.com/Homebrew/homebrew-php
46
 *      - install php5-redis: brew install php55-redis
47
 *
48
 * @link http://redis.io/commands/sort#using-hashes-in-codebycode-and-codegetcode to get information 
49
 *      about how to query a set or list of referenced keys
50
 * @link https://stackoverflow.com/questions/10155398/getting-multiple-key-values-from-redis methods of retrieval of data in redis
51
 * @link https://github.com/antirez/lamernews redis implementation with interesting datastructures (by the redis author)
52
 *
53
 * @author Rolf Vreijdenberger
54
 *
55
 */
56
class Redis extends Adapter implements Loader {
57
58
    const DATABASE_DEFAULT = 0;//default database to use
59
    const KEY_PREFIX_IZZUM = 'izzum:';//default key prefix
60
    
61
    /**
62
     * default key for configuration of machines
63
     */
64
    const KEY_CONFIGURATION = 'configuration';
65
    
66
    /**
67
     * configuration:<machine-name>
68
     */
69
    const KEY_CONFIGURATION_SPECIFIC = '%s:%s';
70
    /**
71
     * set of entities in a machine:  <machine>
72
     */
73
    const KEY_ENTITYIDS = 'entities:%s:ids';
74
    /**
75
     * set of entities per state:  <machine>, <state>
76
     */
77
    const KEY_CURRENT_STATES = 'entities:%s:states:%s';
78
    
79
    /**
80
     * state of an entity: <machine>, <id>
81
     */
82
    const KEY_ENTITY_STATE = 'entities:%s:state:%s';
83
84
    
85
    /**
86
     * this key stores the count of all transitions and is used as the id that references the canonical form.
87
     * transitions are added by incrementing this counter and using that id as key to the entries of the canonical forms
88
     * of the transitions (stored as a redis hash)
89
     */
90
    const KEY_COUNTERS_TRANSITIONS_ALL = 'counters:transitions:all';
91
92
    /**
93
     * the keys that stores the canonical form of the transitions, storing the transition data.
94
     * all other transitions will reference the id/counter of the canonical form
95
     */
96
    const KEY_TRANSITIONS_CANONICAL = 'transitions:canonical:%s';//hash: $counter (KEY_COUNTERS_TRANSITIONS_ALL)
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
97
98
    /**
99
     * the next keys store id's in sorted sets, scored by timestamp. the id's reference the canonical form.
100
     * the keys provide different views on the data that can easily be retrieved by using:
101
     * - sort: the sort command allows you to reference multiple transitions as referenced in a list or set, in one command
102
     * - zcount: count the elements in a sorted set. this gives you an easy count on the number of transitions per
103
     *      machine, state, entity etc. since those are all stored.
104
     */
105
    const KEY_TRANSITIONS_ALL = 'transitions:all:normal';//sorted set
106
    const KEY_TRANSITIONS_MACHINES = 'transitions:machines:normal:%s';//sorted set: $machine
107
    const KEY_TRANSITIONS_STATES = 'transitions:states:normal:%s:%s';//sorted set: $machine, $state
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
108
    const KEY_TRANSITIONS_ENTITIES = 'transitions:entities:normal:%s:%s';//sorted set: $machine, $entity_id
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
109
    const KEY_TRANSITIONS_ALL_FAILED = 'transitions:all:failed';//sorted set
110
    const KEY_TRANSITIONS_MACHINES_FAILED = 'transitions:machines:failed:%s';//sorted set:$machine
111
    const KEY_TRANSITIONS_STATES_FAILED = 'transitions:states:failed:%s:%s';//sorted set: $machine, $state
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
112
    const KEY_TRANSITIONS_ENTITIES_FAILED = 'transitions:entities:failed:%s:%s';//sorted set: $machine, $entity_id
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
113
    
114
    
115
    
116
    private $host;
117
    private $port;
118
    private $timeout;
119
    private $reserved;
120
    private $retry;
121
    private $socket;
122
    private $password;
123
    private $database;
124
    private $prefix;
125
    private $configuration_key;
126
127
    /**
128
     * connected and optionally authenticated redis connection.
129
     * @var \Redis
130
     */
131
    private $redis;
132
133
134
    /**
135
     * The constructor accepts default connection parameters.
136
     *
137
     * You can also use an existing \Redis instance used by your application. 
138
     * Just construct without parameters and call 'setConnection($instance)' before doing anything else.
139
     *
140
     * You can also use a unix domain socket. Just construct without parameters
141
     * and call 'setUnixDomainSocket' before doing anything else.
142
     *
143
     * @param string $host optional
144
     * @param int $port optional
145
     * @param float $timeout value in seconds. default is 0 meaning unlimited
146
     * @param string $reserved should be NULL if $retry is specified
147
     * @param int $retry value in milliseconds
148
     */
149 3
    public function __construct($host = '127.0.0.1', $port = 6379, $timeout = 0, $reserved = null, $retry = null)
150
    {
151
152 3
        $this->host = $host;
153 3
        $this->port = $port;
154 3
        $this->timeout = $timeout;
155 3
        $this->reserved = $reserved;
156 3
        $this->retry = $retry;
157 3
        $this->socket = null;
158 3
        $this->setPrefix(self::KEY_PREFIX_IZZUM);
159 3
        $this->setConfigurationKey(self::KEY_CONFIGURATION);
160 3
        $this->setDatabase(self::DATABASE_DEFAULT);
161
162
163 3
    }
164
165
    public function setUnixDomainSocket($socket)
166
    {
167
        $this->socket = $socket;
168
        $this->host = null;
169
        $this->port = null;
170
        $this->timeout = null;
171
        $this->reserved = null;
172
        $this->retry = null;
173
        $this->socket = null;
174
    }
175
176
    /**
177
     * set password to authenticate to the redis server
178
     * @param string $password
179
     */
180
    public function setPassword($password) {
181
        $this->password = $password;
182
    }
183
    
184
    /**
185
     * set the redis database. in case there is an active connection, it switches the database.
186
     * @param int $database a redis database is an integer starting from 0 (the default)
187
     */
188 3
    public function setDatabase($database) {
189 3
        if($this->redis) {
190
            $this->redis->select($database);
191
        }
192 3
        $this->database = $database;
193 3
    }
194
195
    /**
196
     * set the redis connection explicitely, useful if you want to share the
197
     * redis instance when it is created outside this class.
198
     * @param \Redis $redis a connected (and authenticated) redis instance
199
     */
200
    public function setConnection(\Redis $redis)
201
    {
202
        $this->redis = $redis;
203
    }
204
205
    /**
206
     * set the key prefix to be used for all redis keys
207
     * @param string $prefix
208
     */
209 3
    final public function setPrefix($prefix) {
210 3
        if($this->redis) {
211 1
            $this->redis->setOption(\Redis::OPT_PREFIX, $prefix);
212 1
        }
213 3
        $this->prefix = $prefix;
214 3
    }
215
    
216
    /**
217
     * set the configuration key to be used for storing a json string of machine configurations.
218
     * @param string $key
219
     */
220 3
    final public function setConfigurationKey($key) {
221 3
        $this->configuration_key = $key;
222 3
    }
223
    
224
    /**
225
     * get the configuration key used for storing a json string of machine configurations.
226
     * @return string $key
227
     */
228 3
    final public function getConfigurationKey() {
229 3
        return $this->configuration_key;
230
    }
231
232
    /**
233
     * get the prefix for all keys used
234
     * @return string
235
     */
236 3
    final public function getPrefix()
237
    {
238 3
        return $this->prefix;
239
    }
240
241
    /**
242
     * Gets a lazy loaded \Redis instance that is connected and optionally authenticated.
243
     *
244
     * @throws Exception
245
     * @return \Redis
246
     */
247 3
    public function getRedis() {
248
        //lazy loaded connection
249
        try {
250 3
            if($this->redis === null) {
251 3
                $this->redis = new \Redis();
252 3
                if($this->socket) {
253
                    $connected = $this->redis->connect($this->socket);
254
                } else { /* default connection with different parameters */
255 3
                    if($this->retry !== null) {
256
                        $connected = $this->redis->connect($this->host, $this->port, $this->timeout, null, $this->retry);
257
                    } else {
258 3
                        if($this->reserved !== null) {
259
                            $connected = $this->redis->connect($this->host, $this->port, $this->timeout, $this->reserved);
260
                        } else {
261 3
                            $connected = $this->redis->connect($this->host, $this->port, $this->timeout);
262
                        }
263
                    }
264
                }
265 3
                if(!$connected) {
266
                    $this->redis = null;
267
                    throw new Exception('connection not made', Exception::PERSISTENCE_FAILED_TO_CONNECT);
268
                }
269 3
                if($this->password) {
270
                    $authenticated = $this->redis->auth($this->password);
271
                    if(!$authenticated) {
272
                        throw new Exception('authentication failed', Exception::PERSISTENCE_FAILED_TO_CONNECT);
273
                    }
274
                }
275
                //set the database
276 3
                $this->redis->select($this->database);
277 3
                $this->redis->setOption(\Redis::OPT_PREFIX, $this->getPrefix());
278
                //hook for subclass
279 3
                $this->onConnect();
280 3
            }
281
282 3
            return $this->redis;
283
284
        } catch (\Exception $e) {
285
            throw new Exception(
286
                    sprintf("error creating Redis connection: [%s]",
287
                             $e->getMessage()),
288
                    Exception::PERSISTENCE_FAILED_TO_CONNECT);
289
        }
290
291
    }
292
293
    /**
294
     * A hook to use in a subclass.
295
     * you can do you initial setup here if you like.
296
     */
297 3
    protected function onConnect() {
298
        //override if necessary
299 3
    }
300
301
    /**
302
     * {@inheritDoc}
303
     */
304 2
    public function processGetState(Identifier $identifier) {
305 2
        $redis = $this->getRedis();
306
        try {
307
            //get state from key
308 2
            $key = sprintf(self::KEY_ENTITY_STATE, $identifier->getMachine(), $identifier->getEntityId());
309 2
            $state = $redis->get($key);
310 2
        } catch (\Exception $e) {
311
            throw new Exception(sprintf('getting current state failed: [%s]',
312
                    $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
313
        }
314 2
        if(!$state) {
315
            throw new Exception(sprintf('no state found for [%s]. '
316
                    . 'Did you "$machine->add()" it to the persistence layer?',
317
                    $identifier->getId(true)),
318
                    Exception::PERSISTENCE_LAYER_EXCEPTION);
319
        }
320 2
        return $state;
321
    }
322
323
324
    /**
325
     * {@inheritDoc}
326
     */
327 2
    public function isPersisted(Identifier $identifier) {
328
        try {
329 2
            $redis = $this->getRedis();
330
            //get key from known entity ids set
331 2
            $key = sprintf(self::KEY_ENTITYIDS, $identifier->getMachine());
332 2
            return $redis->sismember($key, $identifier->getEntityId());
333
        } catch (\Exception $e) {
334
            throw new Exception(
335
                    sprintf('getting persistence info failed: [%s]',
336
                            $e->getMessage()), Exception::PERSISTENCE_LAYER_EXCEPTION);
337
        }
338
    }
339
340
341
    /**
342
     * {@inheritDoc}
343
     */
344 2
    public function insertState(Identifier $identifier, $state, $message = null)
345
    {
346 2
        $redis = $this->getRedis();
347
        try {
348 2
            $redis->multi(\Redis::MULTI);
349
            //add to set of known id's
350 2
            $key = sprintf(self::KEY_ENTITYIDS, $identifier->getMachine());
351 2
            $redis->sadd($key, $identifier->getEntityId());
352
            //set the state
353 2
            $key = sprintf(self::KEY_ENTITY_STATE, $identifier->getMachine(), $identifier->getEntityId());
354 2
            $redis->set($key, $state);
355
            //set on current states set
356 2
            $key = sprintf(self::KEY_CURRENT_STATES, $identifier->getMachine(), $state);
357 2
            $redis->sadd($key, $identifier->getEntityId());
358 2
            $redis->exec();
359
            
360 2
        } catch (\Exception $e) {
361
            $redis->discard();
362
            throw new Exception(sprintf('query for inserting state failed: [%s]',
363
                    $e->getMessage()),
364
                    Exception::PERSISTENCE_LAYER_EXCEPTION);
365
        }
366 2
    }
367
368
369
370
    /**
371
     * {@inheritDoc}
372
     */
373 2
    public function updateState(Identifier $identifier, $state, $message = null)
374
    {
375
376 2
        $redis = $this->getRedis();
377
        try {
378
            //first, get the current state
379 2
            $key = sprintf(self::KEY_ENTITY_STATE, $identifier->getMachine(), $identifier->getEntityId());
380 2
            $current = $redis->get($key);
381
            
382
            
383
            //now that we have the current state, start multi in pipeline mode for faster execution (single server roundtrip)
384 2
            $redis->multi(\Redis::PIPELINE);
385
            //remove from current state set
386 2
            $key = sprintf(self::KEY_CURRENT_STATES, $identifier->getMachine(), $current);
387 2
            $redis->srem($key, $identifier->getEntityId());
388
            //set the new state
389 2
            $key = sprintf(self::KEY_ENTITY_STATE, $identifier->getMachine(), $identifier->getEntityId());
390 2
            $redis->set($key, $state);
391
            //set on current states set
392 2
            $key = sprintf(self::KEY_CURRENT_STATES, $identifier->getMachine(), $state);
393 2
            $redis->sadd($key, $identifier->getEntityId());
394
            //execute the sequence of redis commands
395 2
            $redis->exec();
396
            
397 2
        } catch (\Exception $e) {
398
            $redis->discard();
399
            throw new Exception(sprintf('updating state failed: [%s]',
400
                    $e->getMessage()),
401
                    Exception::PERSISTENCE_LAYER_EXCEPTION);
402
        }
403 2
    }
404
405
    /**
406
     * {@inheritDoc}
407
     */
408 2
    public function addHistory(Identifier $identifier, $state, $message = null, $is_exception = false)
409
    {
410 2
        $redis = $this->getRedis();
411
        try {
412 2
            $machine = $identifier->getMachine();
413 2
            $entity_id = $identifier->getEntityId();
414
            
415
            //counter for the number of transitions.
416 2
            $key = self::KEY_COUNTERS_TRANSITIONS_ALL;
417
            //this will function as the id of the transition data, stored as a redis hash
418 2
            $counter = $redis->incr($key);
419
            
420
            //now that we have the counter, start a redis multi command in pipeline mode
421 2
            $redis->multi(\Redis::PIPELINE);
422
            
423
            //create the record for the transition to store in a redis hash
424 2
            $timestamp = time();
425 2
            $record = array();
426 2
            $record['state'] = $state;
427 2
            $record['machine'] = $machine;
428 2
            $record['entity_id'] = $entity_id;
429 2
            if($message) {
430 2
                if(is_string($message)) {
431 2
                    $info = new \stdClass();
432 2
                    $info->message = $message;
433 2
                    $message = $info;
434 2
                }
435
                //always json_encode so we can pass objects as messages
436 2
                $message = json_encode($message);
437 2
            }
438 2
            $record['message'] = $message;
439 2
            $record['timestamp'] = $timestamp;
440 2
            $record['datetime'] = date('Y-m-d H:i:s', $timestamp);//ISO_8601
441 2
            $record['exception'] = $is_exception ? 1 : 0;
442 2
            $record['id'] = $counter;
443
            
444
            /*
445
             * set the canonical form of the transition as a hash with the counter as the id.
446
             * This allows you to get data from the canonical from by storing a reference to the transaction id
447
             * in other lists or sets (memory optimization) via:
448
             * sort izzum:transitions:all:normal by nosort get izzum:transitions:canonical:*->message STORE mymessages
449
             * @see http://redis.io/commands/sort
450
             */
451
            
452 2
            $key_hash_id = sprintf(self::KEY_TRANSITIONS_CANONICAL, $counter);
453 2
            $redis->hmset($key_hash_id, $record);
454
            
455
            
456
            //store all transitions referencing the id of the canonical form in sorted sets (keyed by timestamp),
457
            //set manipulations are powerful in redis (diff, union, intersect etc with the exceptions for example)
458 2
            $key = self::KEY_TRANSITIONS_ALL;
459 2
            $redis->zadd($key, $timestamp, $counter);
460 2
            $key = sprintf(self::KEY_TRANSITIONS_MACHINES, $machine);
461 2
            $redis->zadd($key, $timestamp, $counter);
462 2
            $key = sprintf(self::KEY_TRANSITIONS_STATES, $machine, $state);
463 2
            $redis->zadd($key, $timestamp, $counter);
464 2
            $key = sprintf(self::KEY_TRANSITIONS_ENTITIES, $machine, $entity_id);
465 2
            $redis->zadd($key, $timestamp, $counter);
466
            
467
            
468 2
            if($is_exception) {
469
                //store all failed transitions referencing the id of the canonical form in sets
470 1
                $key = self::KEY_TRANSITIONS_ALL_FAILED;
471 1
                $redis->zadd($key, $timestamp, $counter);
472 1
                $key = sprintf(self::KEY_TRANSITIONS_MACHINES_FAILED, $machine);
473 1
                $redis->zadd($key, $timestamp, $counter);
474 1
                $key = sprintf(self::KEY_TRANSITIONS_STATES_FAILED, $machine, $state);
475 1
                $redis->zadd($key, $timestamp, $counter);
476 1
                $key = sprintf(self::KEY_TRANSITIONS_ENTITIES_FAILED, $machine, $entity_id);
477 1
                $redis->zadd($key, $timestamp, $counter);
478 1
            }
479
            //execute the sequence of redis commands
480 2
            $redis->exec();
481
            
482 2
        } catch (\Exception $e) {
483
            $redis->discard();
484
            throw new Exception(sprintf('adding history failed: [%s]',
485
                    $e->getMessage()),
486
                    Exception::PERSISTENCE_LAYER_EXCEPTION);
487
        }
488 2
    }
489
490
    /**
491
     * {@inheritDoc}
492
     */
493 2
    public function getEntityIds($machine, $state = null) {
494 2
        $output = array();
495
        try {
496 2
            $redis = $this->getRedis();
497 2
            if($state !== null) {
498
                //get from set of entities per state
499 1
                $key = sprintf(self::KEY_CURRENT_STATES, $machine, $state);
500 1
                $output = $redis->smembers($key);
501 1
            } else {
502
                //get state directly
503 2
                $key = sprintf(self::KEY_ENTITYIDS, $machine);
504 2
                $output = $redis->smembers($key);
505
            }
506 2
        } catch (\Exception $e) {
507
            throw new Exception($e->getMessage(),
508
                    Exception::PERSISTENCE_LAYER_EXCEPTION, $e);
509
        }
510 2
        return $output;
511
    }
512
513
    /**
514
     * {@inheritDoc}
515
     * Load the statemachine with data from a JSON string.
516
     * the JSON string is stored at the redis key '<prefix:>configuration' by default.
517
     * you can alter the configuration key by using Redis::setPrefix() and Redis::setConfigurationKey()
518
     * 
519
     * First, the key '<prefix>:configuration:<machine-name>' is checked for existence.
520
     * If it exists, take the configuration from that key, else take the configuration form
521
     * the '<prefix>:configuration' key.
522
     * 
523
     * This method can be overriden in a subclass to use another loader when 
524
     * the data is stored in redis in YAML or XML form for example.
525
     * You could use the ReaderWriterDelegator to use another source to load the configuration from.
526
     */
527 3
    public function load(StateMachine $statemachine) {
528
        //use the JSON loader to load the configuration (see the json schema we expect in JSON::getJSONSchema)
529 3
        $key = $this->getConfigurationKey();
530 3
        $redis = $this->getRedis();
531 3
        $specific_key = sprintf(self::KEY_CONFIGURATION_SPECIFIC, $key, $statemachine->getContext()->getMachine());
532 3
        if($redis->exists($specific_key)){
533 1
            $key = $specific_key;
534 1
        }
535 3
        $loader = new JSON($this->getRedis()->get($key));
536 3
        $count = $loader->load($statemachine);
537 3
        return $count;
538
    }
539
540
541
    /**
542
     * do some cleanup
543
     */
544
    public function __destruct()
545
    {
546
        try {
547
            if($this->redis) {
548
                $this->redis->close();
549
                $this->redis = null;
550
            }
551
        } catch (\Exception $e) {
552
           //nothing we can do about it...
553
        }
554
    }
555
556
    /**
557
     * very very dumb proxy to redis connection. should only be used for testing.
558
     * calls methods on a https://github.com/phpredis/phpredis instance.
559
     * 
560
     * some method calls will only accept an array as the second argument (like hmset)
561
     * 
562
     * This makes it useful to test the redis commands or just use this class as an interface to redis.
563
     * 
564
     * @param string $name name of the method to route to the active redis connection
565
     * @param mixed $arguments
566
     * @return mixed
567
     */
568 3
    public function __call($name, $arguments)
569
    {
570
        //call the method with $name on the \Redis instance
571 3
        return call_user_func_array(array($this->getRedis(), $name), $arguments);
572
    }
573
    
574 2
    public function toString()
575
    {
576 2
        return get_class($this) . ' redis://'. $this->host . ':' . $this->port . '/' . $this->database;
577
    }
578
    
579 2
    public function __toString()
580
    {
581 2
        return $this->toString();
582
    }
583
584
}