• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

optimizely / php-sdk / 4536635044

pending completion
4536635044

Pull #265

github

GitHub
Merge 019774507 into 34bebf03a
Pull Request #265: [FSSDK-8940] Correct return type hints

2871 of 2957 relevant lines covered (97.09%)

63.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.26
/src/Optimizely/Utils/SemVersionConditionEvaluator.php
1
<?php
2
/**
3
 * Copyright 2020, Optimizely
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17

18
namespace Optimizely\Utils;
19

20
use Monolog\Logger;
21
use Optimizely\Enums\CommonAudienceEvaluationLogs as logs;
22

23
class SemVersionConditionEvaluator
24
{
25
    const BUILD_SEPARATOR = '+';
26
    const PRE_RELEASE_SEPARATOR = '-';
27
    const WHITESPACE_SEPARATOR = ' ';
28

29
    /**
30
     * compares targeted version with the provided user version.
31
     *
32
     * @param  string $targetedVersion
33
     * @param  string $userVersion
34
     * @param  object $logger
35
     *
36
     * @return null|int 0 if user's version attribute is equal to the semver condition value,
37
     *                  1 if user's version attribute is greater than the semver condition value,
38
     *                  -1 if user's version attribute is less than the semver condition value,
39
     *                  null if the condition value or user attribute value has an invalid type, or
40
     *                  if there is a mismatch between the user attribute type and the condition
41
     *                  value type.
42
     */
43
    public static function compareVersion($targetedVersion, $userVersion, $logger)
44
    {
45
        $targetedVersionParts = self::splitSemanticVersion($targetedVersion, $logger);
30✔
46
        if ($targetedVersionParts === null) {
30✔
47
            return null;
6✔
48
        }
49
        $userVersionParts = self::splitSemanticVersion($userVersion, $logger);
24✔
50
        if ($userVersionParts === null) {
24✔
51
            return null;
1✔
52
        }
53
        $userVersionPartsCount = count($userVersionParts);
23✔
54
        $isPreReleaseTargetedVersion = self::isPreReleaseVersion($targetedVersion);
23✔
55
        $isPreReleaseUserVersion = self::isPreReleaseVersion($userVersion);
23✔
56

57
        // Up to the precision of targetedVersion, expect version to match exactly.
58
        for ($i = 0; $i < count($targetedVersionParts); $i++) {
23✔
59
            if ($userVersionPartsCount <= $i) {
23✔
60
                if ($isPreReleaseTargetedVersion) {
4✔
61
                    return 1;
1✔
62
                }
63
                return -1;
3✔
64
            } elseif (!is_numeric($userVersionParts[$i])) {
23✔
65
                // Compare strings
66
                if (strcasecmp($userVersionParts[$i], $targetedVersionParts[$i]) < 0) {
7✔
67
                    if ($isPreReleaseTargetedVersion && !$isPreReleaseUserVersion) {
4✔
68
                        return 1;
1✔
69
                    }
70
                    return -1;
3✔
71
                } elseif (strcasecmp($userVersionParts[$i], $targetedVersionParts[$i]) > 0) {
6✔
72
                    if (!$isPreReleaseTargetedVersion && $isPreReleaseUserVersion) {
4✔
73
                        return -1;
×
74
                    }
75
                    return 1;
4✔
76
                }
77
            } elseif (is_numeric($targetedVersionParts[$i])) {
23✔
78
                // both targetedVersionParts and versionParts are digits
79
                if (intval($userVersionParts[$i]) < intval($targetedVersionParts[$i])) {
23✔
80
                    return -1;
9✔
81
                } elseif (intval($userVersionParts[$i]) > intval($targetedVersionParts[$i])) {
22✔
82
                    return 1;
10✔
83
                }
84
            } else {
20✔
85
                return -1;
×
86
            }
87
        }
20✔
88
        // checking if user's version attribute is a prerelease and targeted version isn't
89
        // since pre-release versions have a lower precedence than the associated normal version
90
        if (!$isPreReleaseTargetedVersion && $isPreReleaseUserVersion) {
14✔
91
            return -1;
5✔
92
        }
93
        return 0;
12✔
94
    }
95

96
    /**
97
     * Splits given version into appropriate semantic version array.
98
     *
99
     * @param  string $targetedVersion
100
     *
101
     * @return null|array   array if provided string was successfully split,
102
     *                      null if any issues occured.
103
     */
104
    private static function splitSemanticVersion($version, $logger)
105
    {
106
        if (strpos($version, self::WHITESPACE_SEPARATOR) !== false) {
30✔
107
            $logger->log(Logger::WARNING, sprintf(
2✔
108
                logs::ATTRIBUTE_FORMAT_INVALID
109
            ));
2✔
110
            return null;
2✔
111
        }
112

113
        $targetPrefix = $version;
29✔
114
        $targetSuffix = array();
29✔
115

116
        $separator = null;
29✔
117
        if (self::isPreReleaseVersion($version)) {
29✔
118
            $separator = self::PRE_RELEASE_SEPARATOR;
15✔
119
        } elseif (self::isBuildVersion($version)) {
29✔
120
            $separator = self::BUILD_SEPARATOR;
4✔
121
        }
4✔
122

123
        if ($separator !== null) {
29✔
124
            $targetParts = explode($separator, $version, 2);
16✔
125
            if (count($targetParts) <= 1) {
16✔
126
                $logger->log(Logger::WARNING, sprintf(
×
127
                    logs::ATTRIBUTE_FORMAT_INVALID
128
                ));
×
129
                return null;
×
130
            }
131
            $targetPrefix = $targetParts[0];
16✔
132
            $targetSuffix = array_slice($targetParts, 1, (count($targetParts) - 1));
16✔
133
        }
16✔
134

135
        // Expect a version string of the form x.y.z
136
        $dotCount = substr_count($targetPrefix, ".");
29✔
137
        if ($dotCount > 2) {
29✔
138
            $logger->log(Logger::WARNING, sprintf(
2✔
139
                logs::ATTRIBUTE_FORMAT_INVALID
140
            ));
2✔
141
            return null;
2✔
142
        }
143

144
        $targetedVersionParts = array_filter(explode(".", $targetPrefix), 'strlen');
28✔
145
        $targetedVersionPartsCount = count($targetedVersionParts);
28✔
146

147
        if ($targetedVersionPartsCount !== ($dotCount + 1)) {
28✔
148
            $logger->log(Logger::WARNING, sprintf(
4✔
149
                logs::ATTRIBUTE_FORMAT_INVALID
150
            ));
4✔
151
            return null;
4✔
152
        }
153

154
        foreach ($targetedVersionParts as $val) {
25✔
155
            if (!is_numeric($val)) {
25✔
156
                $logger->log(Logger::WARNING, sprintf(
2✔
157
                    logs::ATTRIBUTE_FORMAT_INVALID
158
                ));
2✔
159
                return null;
2✔
160
            }
161
        }
25✔
162

163
        if ($targetSuffix !== null) {
24✔
164
            return array_merge($targetedVersionParts, $targetSuffix);
24✔
165
        }
166
        return $targetedVersionParts;
×
167
    }
168

169
    /**
170
     * Checks if given string is a prerelease version.
171
     *
172
     * @param  string $version value to be checked.
173
     *
174
     * @return bool true if given version is a prerelease.
175
     */
176
    private static function isPreReleaseVersion($version)
177
    {
178
        //check if string contains prerelease seperator before build separator
179
        $preReleasePos = strpos($version, self::PRE_RELEASE_SEPARATOR);
29✔
180
        if ($preReleasePos === false) {
29✔
181
            return false;
22✔
182
        }
183

184
        $buildPos = strpos($version, self::BUILD_SEPARATOR);
15✔
185
        if ($buildPos === false) {
15✔
186
            return true;
14✔
187
        }
188
        return $buildPos > $preReleasePos;
5✔
189
    }
190

191
    /**
192
     * Checks if given string is a build version.
193
     *
194
     * @param  string $version value to be checked.
195
     *
196
     * @return bool true if given version is a build.
197
     */
198
    private static function isBuildVersion($version)
199
    {
200
        // checks if string contains build seperator before prerelease separator
201
        $buildPos = strpos($version, self::BUILD_SEPARATOR);
23✔
202
        if ($buildPos === false) {
23✔
203
            return false;
21✔
204
        }
205

206
        $preReleasePos = strpos($version, self::PRE_RELEASE_SEPARATOR);
4✔
207
        if ($preReleasePos == false) {
4✔
208
            return true;
3✔
209
        }
210
        return $preReleasePos > $buildPos;
2✔
211
    }
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc