Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ImportOperations
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
6 / 6
11
100.00% covered (success)
100.00%
1 / 1
 add
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 throwExceptionOnBadState
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 runOperations
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 prepare
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 validate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 commit
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Data;
4
5use FileImporter\Exceptions\ImportException;
6use FileImporter\Interfaces\ImportOperation;
7use StatusValue;
8
9/**
10 * @license GPL-2.0-or-later
11 * @author Addshore
12 */
13class ImportOperations implements ImportOperation {
14
15    private const ERROR_EMPTY_OPERATIONS = 'emptyOperations';
16    private const ERROR_OUT_OF_ORDER = 'outOfOrder';
17
18    /** @var ImportOperation[] */
19    private $importOperations = [];
20    /** @var int the state of this object, one of the class constants */
21    private $state = self::BUILDING;
22
23    private const BUILDING = 0;
24    private const PREPARE_RUN = 1;
25    private const VALIDATE_RUN = 2;
26    private const COMMIT_RUN = 3;
27
28    public function add( ImportOperation $importOperation ): void {
29        $this->throwExceptionOnBadState( self::BUILDING );
30        $this->importOperations[] = $importOperation;
31    }
32
33    /**
34     * @param int $expectedState one of the class constants
35     *
36     * @throws ImportException when the expected state doesn't match
37     */
38    private function throwExceptionOnBadState( int $expectedState ): void {
39        if ( $this->state !== $expectedState ) {
40            throw new ImportException(
41                __CLASS__ . ' methods run out of order', self::ERROR_OUT_OF_ORDER );
42        }
43    }
44
45    /**
46     * Run through one phase of import operations and collect status.
47     *
48     * @param int $expectedState State machine must be in this state to begin processing.
49     * @param int $nextState State machine will move to this state once processing begins.
50     * @param bool $stopOnError when True the state machine will exit early if errors are found
51     * @param callable $executor function( ImportOperation ): StatusValue,
52     *  callback to select which phase of the operation to run.
53     * @return StatusValue isOK if all steps succeed.  Accumulates warnings and may
54     *  include a final error explaining why not ok.
55     */
56    private function runOperations(
57        int $expectedState,
58        int $nextState,
59        bool $stopOnError,
60        callable $executor
61    ): StatusValue {
62        if ( !$this->importOperations ) {
63            throw new ImportException(
64                __CLASS__ . ' tried to run empty import operations',
65                self::ERROR_EMPTY_OPERATIONS
66            );
67        }
68
69        $this->throwExceptionOnBadState( $expectedState );
70        $this->state = $nextState;
71
72        $status = StatusValue::newGood();
73        foreach ( $this->importOperations as $importOperation ) {
74            $status->merge( $executor( $importOperation ) );
75
76            if ( $stopOnError && !$status->isOK() ) {
77                break;
78            }
79        }
80
81        return $status;
82    }
83
84    /**
85     * Method to prepare an operation. This will not commit anything to any persistent storage.
86     * @return StatusValue isOK when all steps succeed
87     */
88    public function prepare(): StatusValue {
89        return $this->runOperations(
90            self::BUILDING,
91            self::PREPARE_RUN,
92            true,
93            static function ( ImportOperation $importOperation ) {
94                return $importOperation->prepare();
95            }
96        );
97    }
98
99    /**
100     * Method to validate prepared content that should be committed.
101     * @return StatusValue isOK when all validation succeeds.  Specifics are accumulated
102     *  as errors and warnings.
103     */
104    public function validate(): StatusValue {
105        return $this->runOperations(
106            self::PREPARE_RUN,
107            self::VALIDATE_RUN,
108            false,
109            static function ( ImportOperation $importOperation ) {
110                return $importOperation->validate();
111            }
112        );
113    }
114
115    /**
116     * Commit this operation to persistent storage.
117     * @return StatusValue isOK if all steps succeeded.
118     */
119    public function commit(): StatusValue {
120        return $this->runOperations(
121            self::VALIDATE_RUN,
122            self::COMMIT_RUN,
123            true,
124            static function ( ImportOperation $importOperation ) {
125                return $importOperation->commit();
126            }
127        );
128    }
129
130}