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.

PlayStory_Command   D
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 467
Duplicated Lines 13.7 %

Coupling/Cohesion

Components 1
Dependencies 31

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 31
c 2
b 1
f 0
lcom 1
cbo 31
dl 64
loc 467
rs 4.9

11 Methods

Rating   Name   Duplication   Size   Complexity  
A addStoriesFromFolder() 0 22 3
B __construct() 0 45 1
B processCommand() 26 26 5
B initReporting() 0 24 4
A initSignalHandling() 9 9 1
B initPlayerList() 0 52 7
A addStoryFromFile() 0 19 2
A findStoriesInFolder() 0 19 2
B sigtermHandler() 29 29 2
A summariseStoryList() 0 14 2
B processInsideLegacyHandler() 0 108 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * Copyright (c) 2011-present Mediasift Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   Storyplayer/Cli
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2011-present Mediasift Ltd www.datasift.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://datasift.github.io/storyplayer
42
 */
43
44
namespace DataSift\Storyplayer\Cli;
45
46
use Exception;
47
use RecursiveDirectoryIterator;
48
use RecursiveIteratorIterator;
49
use RecursiveRegexIterator;
50
use RegexIterator;
51
use stdClass;
52
use Phix_Project\CliEngine;
53
use Phix_Project\CliEngine\CliCommand;
54
use Phix_Project\ExceptionsLib1\Legacy_ErrorHandler;
55
use Phix_Project\ExceptionsLib1\Legacy_ErrorException;
56
use DataSift\Stone\ConfigLib\E5xx_ConfigFileNotFound;
57
use DataSift\Stone\ConfigLib\E5xx_InvalidConfigFile;
58
use DataSift\Storyplayer\PlayerLib\E4xx_NoSuchReport;
59
use DataSift\Storyplayer\PlayerLib\PhaseGroup_Player;
60
use DataSift\Storyplayer\PlayerLib\StoryTeller;
61
use DataSift\Storyplayer\PlayerLib\Story_Player;
62
use DataSift\Storyplayer\PlayerLib\Tale_Player;
63
use DataSift\Storyplayer\PlayerLib\TestEnvironment_Player;
64
use DataSift\Storyplayer\Console\DevModeConsole;
65
use DataSift\Storyplayer\Injectables;
66
67
/**
68
 * A command to play a story, or a list of stories
69
 *
70
 * @category  Libraries
71
 * @package   Storyplayer/Cli
72
 * @author    Stuart Herbert <[email protected]>
73
 * @copyright 2011-present Mediasift Ltd www.datasift.com
74
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
75
 * @link      http://datasift.github.io/storyplayer
76
 */
77
class PlayStory_Command extends BaseCommand implements CliSignalHandler
78
{
79
    /**
80
     * should we let background processes survive when we shutdown?
81
     * @var boolean
82
     */
83
    protected $persistProcesses = false;
84
85
    // we need to track this for handling CTRL-C
86
    protected $st;
87
88
    // we track this for convenience
89
    protected $output;
90
91
    // our list of players to execute
92
    protected $playerList;
93
94
    // our injected data / services
95
    // needed for when user presses CTRL+C
96
    protected $injectables;
97
98
    /**
99
     * the environment that we have loaded
100
     *
101
     * @var string
102
     */
103
    protected $envName;
104
105
    public function __construct($injectables)
106
    {
107
        // call our parent
108
        parent::__construct($injectables);
109
110
        // define the command
111
        $this->setName('play-story');
112
        $this->setShortDescription('play a story, or a list of stories');
113
        $this->setLongDescription(
114
            "Use this command to play a single story, or a list of stories defined in a JSON file."
115
            .PHP_EOL
116
        );
117
        $this->setArgsList(array(
118
            "[<story.php|list.json>]" => "run a story, or a list of stories"
119
        ));
120
121
        // the switches that this command supports
122
        $this->setSwitches(array(
123
            new PlayStory_LogJsonSwitch(),
124
            new PlayStory_LogJUnitSwitch(),
125
            new PlayStory_LogTapSwitch(),
126
        ));
127
128
        // add in the features that this command relies on
129
        $this->addFeature(new Feature_ConsoleSupport);
130
        $this->addFeature(new Feature_VerboseSupport);
131
        $this->addFeature(new Feature_ColorSupport);
132
        $this->addFeature(new Feature_DeviceSupport);
133
        $this->addFeature(new Feature_TestEnvironmentConfigSupport);
134
        $this->addFeature(new Feature_SystemUnderTestConfigSupport);
135
        $this->addFeature(new Feature_LocalhostSupport);
136
        $this->addFeature(new Feature_ActiveConfigSupport);
137
        $this->addFeature(new Feature_DefinesSupport);
138
        $this->addFeature(new Feature_PhaseLoaderSupport);
139
        $this->addFeature(new Feature_ProseLoaderSupport);
140
        $this->addFeature(new Feature_PersistReuseTargetSupport);
141
        $this->addFeature(new Feature_PersistDeviceSupport);
142
        $this->addFeature(new Feature_PersistProcessesSupport);
143
        $this->addFeature(new Feature_TestUsersSupport);
144
        $this->addFeature(new Feature_WarnDeprecatedSupport);
145
        $this->addFeature(new Feature_LogInternalEventsSupport);
146
147
        // now setup all of the switches that we support
148
        $this->addFeatureSwitches();
149
    }
150
151
    /**
152
     *
153
     * @param  CliEngine $engine
154
     * @param  array     $params
155
     * @param  Injectables|null $injectables
156
     * @return integer
157
     */
158 View Code Duplication
    public function processCommand(CliEngine $engine, $params = array(), $injectables = null)
159
    {
160
        // we need to wrap our code to catch old-style PHP errors
161
        $legacyHandler = new Legacy_ErrorHandler();
162
163
        // run our code
164
        try {
165
            $returnCode = $legacyHandler->run([$this, 'processInsideLegacyHandler'], [$engine, $params, $injectables]);
166
            return $returnCode;
167
        }
168
        catch (Exception $e) {
169
            $injectables->output->logCliError($e->getMessage());
170
            $engine->options->dev = true;
171
            if (isset($engine->options->dev) && $engine->options->dev) {
172
                $injectables->output->logCliError("Stack trace is:\n\n" . $e->getTraceAsString());
173
            }
174
175
            // stop the browser if available
176
            if (isset($this->st)) {
177
                $this->st->stopDevice();
178
            }
179
180
            // tell the calling process that things did not end well
181
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method processCommand() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
182
        }
183
    }
184
185
    public function processInsideLegacyHandler(CliEngine $engine, $params = array(), $injectables = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
186
    {
187
        // the order we do things:
188
        //
189
        // 1. build up the config we're going to use
190
        //    a. storyplayer.json (already done)
191
        //    c. any additional config file
192
        //    c. test-environment config file
193
        //    d. per-device config file
194
        //
195
        // 2. override from the command-line
196
        //    a. -D switches
197
        //    b. persistent processes
198
        //
199
        // 3. build up the list of stories to run
200
        //    a. test environment setup
201
        //    b. one or more stories
202
        //    c. test environment teardown
203
        //
204
        // 4. setup any remaining services
205
        //    a. phase loading
206
        //    b. prose loading
207
        //    c. report loader
208
        //
209
        // 5. setup the output channels
210
        //    a. the console (i.e. --dev mode)
211
        //    b. report-to-file plugins
212
213
        // process the common functionality
214
        $this->initFeaturesBeforeModulesAvailable($engine);
215
216
        // now it is safe to create our shorthand
217
        $runtimeConfig        = $injectables->getRuntimeConfig();
218
        $runtimeConfigManager = $injectables->getRuntimeConfigManager();
219
        $output               = $injectables->output;
220
221
        // save the output for use in other methods
222
        $this->output = $output;
223
224
        // setup reporting modules
225
        $this->initReporting($engine, $injectables);
226
227
        // at this point, all of the services / data held in $injectables
228
        // has been initialised and is ready for use
229
        //
230
        // what's left is the stuff that needs initialising in phases
231
        // or $st
232
233
        // create a new StoryTeller object
234
        $st = new StoryTeller($injectables);
235
236
        // remember our $st object, as we'll need it for our
237
        // shutdown function
238
        $this->st = $st;
239
240
        // now that we have $st, we can initialise any feature that
241
        // wants to use our modules
242
        $this->initFeaturesAfterModulesAvailable($st, $engine, $injectables);
243
244
        // install signal handling, now that $this->st is defined
245
        //
246
        // we wouldn't want signal handling called out of order :)
247
        $this->initSignalHandling($injectables);
248
249
        // build our list of players to run
250
        $this->initPlayerList($engine, $injectables, $params);
251
252
        // let's keep score :)
253
        $startTime = microtime(true);
254
255
        // and we're ready to tell the world that we're here
256
        $output->startStoryplayer(
257
            $engine->getAppVersion(),
258
            $engine->getAppUrl(),
259
            $engine->getAppCopyright(),
260
            $engine->getAppLicense()
261
        );
262
263
        // $this->playerList contains one or more things to play
264
        //
265
        // let's play each of them in order
266
        foreach ($this->playerList as $player)
267
        {
268
            // execute each player in turn
269
            //
270
            // they may also have their own list of nested players
271
            $player->play($st, $injectables);
272
273
            // make sure the test device has been stopped
274
            // (it may have been persisted by the story)
275
            //
276
            // we do not allow the test device to persist between
277
            // top-level players
278
            $st->stopDevice();
279
        }
280
281
        // write out any changed runtime config to disk
282
        $runtimeConfigManager->saveRuntimeConfig($runtimeConfig, $output);
283
284
        // how long did that take?
285
        $duration = microtime(true) - $startTime;
286
287
        // tell the output plugins that we're all done
288
        $retval = $output->endStoryplayer($duration);
289
290
        // all done
291
        return $retval;
292
    }
293
294
    // ==================================================================
295
    //
296
    // the individual initX() methods
297
    //
298
    // these are processed *after* the objects defined in the
299
    // CommonFunctionalitySupport trait have been initialised
300
    //
301
    // ------------------------------------------------------------------
302
303
    /**
304
     *
305
     * @param  CliEngine   $engine
306
     * @param  Injectables $injectables
307
     * @return void
308
     */
309
    protected function initReporting(CliEngine $engine, Injectables $injectables)
310
    {
311
        // are there any reporting modules to be loaded?
312
        if (!isset($engine->options->reports)) {
313
            // no
314
            return;
315
        }
316
317
        // setup the reports that have been requested
318
        $injectables->initReportLoaderSupport($injectables);
319
        foreach ($engine->options->reports as $reportName => $reportFilename)
320
        {
321
            try {
322
                $report = $injectables->reportLoader->loadReport($reportName, [ 'filename' => $reportFilename]);
323
            }
324
            catch (E4xx_NoSuchReport $e) {
325
                $injectables->output->logCliError("no such report '{$reportName}'");
326
                exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method initReporting() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
327
            }
328
            $injectables->output->usePluginInSlot($report, $reportName);
329
        }
330
331
        // all done
332
    }
333
334
    /**
335
     *
336
     * @param  Injectables $injectables
337
     * @return void
338
     */
339 View Code Duplication
    protected function initSignalHandling(Injectables $injectables)
340
    {
341
        // we need to remember the injectables, for when we handle CTRL+C
342
        $this->injectables = $injectables;
343
344
        // setup signal handling
345
        pcntl_signal(SIGTERM, array($this, 'sigtermHandler'));
346
        pcntl_signal(SIGINT , array($this, 'sigtermHandler'));
347
    }
348
349
    /**
350
     *
351
     * @param  CliEngine   $cliEngine
352
     * @param  Injectables $injectables
353
     * @param  array       $cliParams
354
     * @return void
355
     */
356
    protected function initPlayerList(CliEngine $cliEngine, Injectables $injectables, $cliParams)
357
    {
358
        // our list of stories to play
359
        $this->playerList = [];
360
361
        // do we have any parameters at this point?
362
        if (empty($cliParams)) {
363
            $msg = "no stories listed on the command-line." . PHP_EOL . PHP_EOL
364
                 . "see 'storyplayer help play-story' for required params" . PHP_EOL;
365
            $this->output->logCliError($msg);
366
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method initPlayerList() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
367
        }
368
369
        // keep track of the stories to play
370
        $storiesToPlay = [];
371
372
        foreach ($cliParams as $cliParam) {
373
            // figure out what to do?
374
            if (is_dir($cliParam)) {
375
                $storiesToPlay = array_merge($storiesToPlay, $this->addStoriesFromFolder($cliEngine, $injectables, $cliParam));
376
            }
377
            else if (is_file($cliParam)) {
378
                // are we loading a story, or a list of stories?
379
                $paramParts  = explode('.', $cliParams[0]);
380
                $paramSuffix = end($paramParts);
381
382
                switch ($paramSuffix) {
383
                    case 'php':
384
                        $storiesToPlay = array_merge($storiesToPlay, $this->addStoryFromFile($cliEngine, $injectables, $cliParam));
385
                        break;
386
387
                    default:
388
                        $this->output->logCliError("unsupported story file '{$cliParam}'");
389
                        exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method initPlayerList() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
390
                }
391
            }
392
            else {
393
                // if we get here, we've no idea what to do
394
                $this->output->logCliError("no such file: '{$cliParam}'");
395
                exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method initPlayerList() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
396
            }
397
        }
398
399
        // did we find any stories to play?
400
        if (count($storiesToPlay) == 0) {
401
            $this->output->logCliError("no stories to play :(");
402
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method initPlayerList() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
403
        }
404
405
        // wrap all of the stories in a TestEnvironment
406
        $this->playerList[] = new TestEnvironment_Player($storiesToPlay, $injectables);
407
    }
408
409
    // ==================================================================
410
    //
411
    // Story loading
412
    //
413
    // ------------------------------------------------------------------
414
415
    protected function addStoryFromFile(CliEngine $engine, Injectables $injectables, $storyFile)
0 ignored issues
show
Unused Code introduced by
The parameter $engine is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
416
    {
417
        // warn the user if the story file doesn't end in 'Story.php'
418
        //
419
        // this is because Storyplayer will ignore the file if you
420
        // point Storyplayer at a folder instead of a specific file
421
        if (substr($storyFile, -9) != 'Story.php') {
422
            $msg = "your story should end in 'Story.php', but it does not" . PHP_EOL;
423
            $this->output->logCliWarning($msg);
424
        }
425
426
        // these are the players we want to execute for the story
427
        $return = [
428
            new Story_Player($storyFile, $injectables),
429
        ];
430
431
        // all done
432
        return $return;
433
    }
434
435
    protected function addStoriesFromFolder(CliEngine $engine, Injectables $injectables, $folder)
0 ignored issues
show
Unused Code introduced by
The parameter $engine is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
436
    {
437
        // find everything under the folder
438
        $filenames = $this->findStoriesInFolder($folder);
439
440
        // did we find anything?
441
        if (!count($filenames)) {
442
            $msg = "no stories found in '{$folder}'" . PHP_EOL . PHP_EOL
443
                 . "do your stories' filenames end in 'Story.php'?";
444
            $this->output->logCliError($msg);
445
            exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method addStoriesFromFolder() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
446
        }
447
448
        // create a set of story players
449
        $storiesToPlay = [];
450
        foreach ($filenames as $filename) {
451
            $storiesToPlay[] = new Story_Player($filename, $injectables);
452
        }
453
454
        // all done
455
        return $storiesToPlay;
456
    }
457
458
    protected function findStoriesInFolder($folder)
459
    {
460
        // use the SPL to do the heavy lifting
461
        $dirIter = new RecursiveDirectoryIterator($folder);
462
        $recIter = new RecursiveIteratorIterator($dirIter);
463
        $regIter = new RegexIterator($recIter, '/^.+Story\.php$/i', RegexIterator::GET_MATCH);
464
465
        // what happened?
466
        $filenames = [];
467
        foreach ($regIter as $match) {
468
            $filenames[] = $match[0];
469
        }
470
471
        // let's get the list into some semblance of order
472
        sort($filenames);
473
474
        // all done
475
        return $filenames;
476
    }
477
478
    // ==================================================================
479
    //
480
    // SIGNAL handling
481
    //
482
    // ------------------------------------------------------------------
483
484
    /**
485
     *
486
     * @param  integer $signo
487
     * @return void
488
     */
489 View Code Duplication
    public function sigtermHandler($signo)
490
    {
491
        // tell the user what is happening
492
        echo PHP_EOL;
493
        echo "============================================================" . PHP_EOL;
494
        echo "USER ABORT!!" . PHP_EOL;
495
496
        // do we skip destroying the test environment?
497
        if ($this->st->getPersistTestEnvironment()) {
498
            echo PHP_EOL . "* Warning: NOT destroying test environment" . PHP_EOL
499
                 .         "           --reuse-target flag is set" . PHP_EOL;
500
        }
501
502
        // cleanup
503
        echo PHP_EOL . "Cleaning up: ";
504
        $phasesPlayer = new PhaseGroup_Player();
505
        $phasesPlayer->playPhases(
506
            "user abort",
507
            $this->st,
508
            $this->injectables,
509
            $this->injectables->activeConfig->getData('storyplayer.phases.userAbort'),
510
            null
511
        );
512
513
        echo " done" . PHP_EOL . "============================================================" . PHP_EOL . PHP_EOL;
514
515
        // force a clean shutdown
516
        exit(1);
0 ignored issues
show
Coding Style Compatibility introduced by
The method sigtermHandler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
517
    }
518
519
    // ==================================================================
520
    //
521
    // legacy code goes here
522
    //
523
    // everything below here is old code that needs stripping out
524
    // before we release v1.6
525
    //
526
    // ------------------------------------------------------------------
527
528
    protected function summariseStoryList($storyResults)
529
    {
530
        // we need to make a pronouncement about the whole list of stories
531
532
        echo "\n";
533
        echo "============================================================\n";
534
        echo "FINAL RESULTS\n";
535
        echo "\n";
536
537
        foreach ($storyResults as $result)
538
        {
539
            echo Story_Player::$outcomeToText[$result->resultCode] . " :: " . $result->story->getName() . "\n";
540
        }
541
    }
542
543
}
544