Passed
Branch main (a5ccba)
by Gaetano
04:40
created

NonScalarReferenceSetterTrait::getSelfName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use Kaliop\eZMigrationBundle\API\Collection\AbstractCollection;
6
use Kaliop\eZMigrationBundle\API\Exception\InvalidMatchResultsNumberException;
7
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
8
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
9
10
/**
11
 * A trait used by Executors which allow to set non-scalar values to references.
12
 * ATM besides scalars we support arrays and collections as reference values
13
 */
14
trait NonScalarReferenceSetterTrait
15
{
16
    // traits can not have constants...
17
18
    static $RESULT_TYPE_SINGLE = 1;
19
    static $RESULT_TYPE_MULTIPLE = 2;
20
    static $RESULT_TYPE_ANY = 3;
21
22
    static $EXPECT_NONE = 'none';
23
    static $EXPECT_ONE = 'one';
24
    static $EXPECT_ANY = 'any';
25
    static $EXPECT_MANY = 'many';
26
    static $EXPECT_UNSPECIFIED = 'unspecified';
27
28
    /**
29
     * @param mixed $results
30
     * @param MigrationStep $step
31
     * @throws InvalidMatchResultsNumberException
32
     * @throws InvalidStepDefinitionException
33
     */
34 35
    protected function validateResultsCount($results, $step)
35
    {
36
        // q: what if we get a scalar result but we expect an array/collection ?
37
        // q2: why not just check for Countable interface instead of AbstractCollection? Or at least allow ArrayIterators and ObjectIterators
38 35
        if (is_array($results) || $results instanceof AbstractCollection) {
39 35
            $expectedResultsCount = $this->expectedResultsCount($step);
40
            switch ($expectedResultsCount) {
41 33
                case self::$EXPECT_UNSPECIFIED:
42 20
                case self::$EXPECT_ANY:
43 27
                    break;
44 20
                case self::$EXPECT_MANY:
45 4
                    if (count($results) == 0) {
46 2
                        throw new InvalidMatchResultsNumberException($this->getSelfName() . ' found no matching ' . $this->getResultsName($results) . 's but expects at least one');
47
                    }
48 2
                    break;
49
                default:
50 16
                    $count = count($results);
51 16
                    if ($count != $expectedResultsCount) {
52 3
                        throw new InvalidMatchResultsNumberException($this->getSelfName() . " found $count matching " . $this->getResultsName($results) . "s but expects $expectedResultsCount");
53
                    }
54
            }
55
        }
56 28
    }
57
58
    /**
59
     * @param MigrationStep $step
60
     * @return int
61
     * @throws InvalidStepDefinitionException
62
     */
63 30
    protected function expectedResultsType($step)
64
    {
65 30
        switch ($this->expectedResultsCount($step)) {
66 30
            case 1:
67 25
                return self::$RESULT_TYPE_SINGLE;
68 11
            case 0:
69
                // note: we consider '0 results' as a specific case of a multi-valued result set; we might want to
70
                // to give it its own RESULT_TYPE...
71 11
                return self::$RESULT_TYPE_MULTIPLE;
72
            case self::$EXPECT_UNSPECIFIED:
73
                return self::$RESULT_TYPE_ANY;
74
            default: // any, many, 2..N
75
                return self::$RESULT_TYPE_MULTIPLE;
76
        }
77
    }
78
79
    /**
80
     * @param MigrationStep $step
81
     * @return int|string 'unspecified', 'any', 'many' or a number 0..PHP_MAX_INT
82
     * @throws InvalidStepDefinitionException
83
     */
84 42
    protected function expectedResultsCount($step)
85
    {
86 42
        if (isset($step->dsl['expect'])) {
87 8
            switch ($step->dsl['expect']) {
88 8
                case self::$EXPECT_NONE:
89 8
                case '0':
90 1
                    return 0;
91 8
                case self::$EXPECT_ONE:
92 5
                case '1':
93 5
                    return 1;
94 4
                case self::$EXPECT_ANY:
95 4
                case self::$EXPECT_MANY:
96 4
                    return $step->dsl['expect'];
97
                default:
98
                    if ((is_int($step->dsl['expect']) || ctype_digit($step->dsl['expect'])) && $step->dsl['expect'] >= 0) {
99
                        return (int)$step->dsl['expect'];
100
                    }
101
                    throw new InvalidStepDefinitionException("Invalid value for 'expect element: {$step->dsl['expect']}");
102
            }
103
        }
104
105
        // BC
106 36
        if (isset($step->dsl['references_type'])) {
107 1
            switch ($step->dsl['references_type']) {
108 1
                case 'array':
109 1
                    return self::$EXPECT_ANY;
110
                case 'scalar':
111
                    return 1;
112
                default:
113
                    throw new InvalidStepDefinitionException('Unexpected value for references_type element: ' . $step->dsl['references_type']);
114
            }
115
        }
116
117
        // if there are references to set, except the always_scalar ones, then we want a single result
118
        // (unless the user told us so via the 'expect' tag)
119 35
        if (isset($step->dsl['references']) && $this->hasNonScalarReferences($step->dsl['references'])) {
120 25
            return 1;
121
        }
122
123 25
        return self::$EXPECT_UNSPECIFIED;
124
    }
125
126
    /**
127
     * @param array $referencesDefinition
128
     * @return bool
129
     * @throws InvalidStepDefinitionException
130
     */
131 30
    protected function hasNonScalarReferences($referencesDefinition)
132
    {
133 30
        foreach ($referencesDefinition as $key => $referenceDefinition) {
134 30
            $referenceDefinition = $this->parseReferenceDefinition($key, $referenceDefinition);
0 ignored issues
show
Bug introduced by
It seems like parseReferenceDefinition() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

134
            /** @scrutinizer ignore-call */ 
135
            $referenceDefinition = $this->parseReferenceDefinition($key, $referenceDefinition);
Loading history...
135 28
            if (!$this->isScalarReference($referenceDefinition))
136
            {
137 25
                return true;
138
            }
139
        }
140
141 8
        return false;
142
    }
143
144
    /**
145
     * @param array $referenceDefinition
146
     * @return bool
147
     */
148
    protected abstract function isScalarReference($referenceDefinition);
149
150
    /**
151
     * Verifies compatibility between the definition of the references to be set and the data set to extract them from.
152
     * NB: for multivalued/array refs, we assume that the users by default expect at least one value.
153
     * NB: for scalar results we do not validate anything, as they are always valid (we do not coerce them to arrays)
154
     * @param AbstractCollection|array|mixed $results
155
     * @param array $referencesDefinition
156
     * @param MigrationStep $step
157
     * @return void throws when incompatibility is found
158
     * @todo we should encapsulate the whole info about refs to be set in a single data structure, instead of poking inside $step...
159
     * @deprecated
160
     */
161
    /*protected function insureResultsCountCompatibility($results, $referencesDefinition, $step)
162
    {
163
        if (!$this->hasNonScalarReferences($referencesDefinition)) {
164
            return;
165
        }
166
167
        if (is_array($results) || $results instanceof AbstractCollection) {
168
            if (count($results) > 1 && !$this->allowMultipleResults($step)) {
169
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for multiple ' . $this->getResultsName($results) . 's');
170
            }
171
            if (count($results) == 0 && !$this->allowEmptyResults($step)) {
172
                throw new \InvalidArgumentException($this->getSelfName() . ' does not support setting references for no ' . $this->getResultsName($results) . 's');
173
            }
174
        }
175
    }*/
176
177
    /**
178
     * @return string
179
     */
180 5
    protected function getSelfName()
181
    {
182 5
        $className = get_class($this);
183 5
        $array = explode('\\', $className);
184 5
        $className = end($array);
185
        // CamelCase to Camel Case using negative look-behind in regexp
186 5
        return preg_replace('/(?<!^)[A-Z]/', ' $0', $className);
187
    }
188
189
    /**
190
     * @param array|object $collection
191
     * @return string
192
     */
193 5
    protected function getResultsName($collection)
194
    {
195 5
        if (is_array($collection)) {
196 1
            return 'result';
197
        }
198
199 4
        $className = get_class($collection);
200 4
        $array = explode('\\', $className);
201 4
        $className = str_replace('Collection', '', end($array));
202
        // CamelCase to snake case using negative look-behind in regexp
203 4
        return strtolower(preg_replace('/(?<!^)[A-Z]/', ' $0', $className));
204
    }
205
}
206