1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Php;
4:
5: use function floor;
6:
7: /**
8: * @api
9: */
10: final class PhpVersion
11: {
12:
13: public const SOURCE_RUNTIME = 1;
14: public const SOURCE_CONFIG = 2;
15: public const SOURCE_COMPOSER_PLATFORM_PHP = 3;
16: public const SOURCE_UNKNOWN = 4;
17:
18: /**
19: * @param self::SOURCE_* $source
20: */
21: public function __construct(private int $versionId, private int $source = self::SOURCE_UNKNOWN)
22: {
23: }
24:
25: /**
26: * @return self::SOURCE_*
27: */
28: public function getSource(): int
29: {
30: return $this->source;
31: }
32:
33: public function getSourceLabel(): string
34: {
35: switch ($this->source) {
36: case self::SOURCE_RUNTIME:
37: return 'runtime';
38: case self::SOURCE_CONFIG:
39: return 'config';
40: case self::SOURCE_COMPOSER_PLATFORM_PHP:
41: return 'config.platform.php in composer.json';
42: }
43:
44: return 'unknown';
45: }
46:
47: public function getVersionId(): int
48: {
49: return $this->versionId;
50: }
51:
52: public function getMajorVersionId(): int
53: {
54: return (int) floor($this->versionId / 10000);
55: }
56:
57: public function getMinorVersionId(): int
58: {
59: return (int) floor(($this->versionId % 10000) / 100);
60: }
61:
62: public function getPatchVersionId(): int
63: {
64: return (int) floor($this->versionId % 100);
65: }
66:
67: public function getVersionString(): string
68: {
69: $first = $this->getMajorVersionId();
70: $second = $this->getMinorVersionId();
71: $third = $this->getPatchVersionId();
72:
73: return $first . '.' . $second . ($third !== 0 ? '.' . $third : '');
74: }
75:
76: public function supportsNullCoalesceAssign(): bool
77: {
78: return $this->versionId >= 70400;
79: }
80:
81: public function supportsParameterContravariance(): bool
82: {
83: return $this->versionId >= 70400;
84: }
85:
86: public function supportsReturnCovariance(): bool
87: {
88: return $this->versionId >= 70400;
89: }
90:
91: public function supportsNoncapturingCatches(): bool
92: {
93: return $this->versionId >= 80000;
94: }
95:
96: public function supportsNativeUnionTypes(): bool
97: {
98: return $this->versionId >= 80000;
99: }
100:
101: public function deprecatesRequiredParameterAfterOptional(): bool
102: {
103: return $this->versionId >= 80000;
104: }
105:
106: public function deprecatesRequiredParameterAfterOptionalNullableAndDefaultNull(): bool
107: {
108: return $this->versionId >= 80100;
109: }
110:
111: public function deprecatesRequiredParameterAfterOptionalUnionOrMixed(): bool
112: {
113: return $this->versionId >= 80300;
114: }
115:
116: public function supportsLessOverridenParametersWithVariadic(): bool
117: {
118: return $this->versionId >= 80000;
119: }
120:
121: public function supportsThrowExpression(): bool
122: {
123: return $this->versionId >= 80000;
124: }
125:
126: public function supportsClassConstantOnExpression(): bool
127: {
128: return $this->versionId >= 80000;
129: }
130:
131: public function supportsLegacyConstructor(): bool
132: {
133: return $this->versionId < 80000;
134: }
135:
136: public function supportsPromotedProperties(): bool
137: {
138: return $this->versionId >= 80000;
139: }
140:
141: public function supportsParameterTypeWidening(): bool
142: {
143: return $this->versionId >= 70200;
144: }
145:
146: public function supportsUnsetCast(): bool
147: {
148: return $this->versionId < 80000;
149: }
150:
151: public function supportsNamedArguments(): bool
152: {
153: return $this->versionId >= 80000;
154: }
155:
156: public function throwsTypeErrorForInternalFunctions(): bool
157: {
158: return $this->versionId >= 80000;
159: }
160:
161: public function throwsValueErrorForInternalFunctions(): bool
162: {
163: return $this->versionId >= 80000;
164: }
165:
166: public function supportsHhPrintfSpecifier(): bool
167: {
168: return $this->versionId >= 80000;
169: }
170:
171: public function isEmptyStringValidAliasForNoneInMbSubstituteCharacter(): bool
172: {
173: return $this->versionId < 80000;
174: }
175:
176: public function supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter(): bool
177: {
178: return $this->versionId >= 70200;
179: }
180:
181: public function isNumericStringValidArgInMbSubstituteCharacter(): bool
182: {
183: return $this->versionId < 80000;
184: }
185:
186: public function isNullValidArgInMbSubstituteCharacter(): bool
187: {
188: return $this->versionId >= 80000;
189: }
190:
191: public function isInterfaceConstantImplicitlyFinal(): bool
192: {
193: return $this->versionId < 80100;
194: }
195:
196: public function supportsFinalConstants(): bool
197: {
198: return $this->versionId >= 80100;
199: }
200:
201: public function supportsReadOnlyProperties(): bool
202: {
203: return $this->versionId >= 80100;
204: }
205:
206: public function supportsEnums(): bool
207: {
208: return $this->versionId >= 80100;
209: }
210:
211: public function supportsPureIntersectionTypes(): bool
212: {
213: return $this->versionId >= 80100;
214: }
215:
216: public function supportsCaseInsensitiveConstantNames(): bool
217: {
218: return $this->versionId < 80000;
219: }
220:
221: public function hasStricterRoundFunctions(): bool
222: {
223: return $this->versionId >= 80000;
224: }
225:
226: public function hasTentativeReturnTypes(): bool
227: {
228: return $this->versionId >= 80100;
229: }
230:
231: public function supportsFirstClassCallables(): bool
232: {
233: return $this->versionId >= 80100;
234: }
235:
236: public function supportsArrayUnpackingWithStringKeys(): bool
237: {
238: return $this->versionId >= 80100;
239: }
240:
241: public function throwsOnInvalidMbStringEncoding(): bool
242: {
243: return $this->versionId >= 80000;
244: }
245:
246: public function supportsPassNoneEncodings(): bool
247: {
248: return $this->versionId < 70300;
249: }
250:
251: public function producesWarningForFinalPrivateMethods(): bool
252: {
253: return $this->versionId >= 80000;
254: }
255:
256: public function deprecatesDynamicProperties(): bool
257: {
258: return $this->versionId >= 80200;
259: }
260:
261: public function strSplitReturnsEmptyArray(): bool
262: {
263: return $this->versionId >= 80200;
264: }
265:
266: public function supportsDisjunctiveNormalForm(): bool
267: {
268: return $this->versionId >= 80200;
269: }
270:
271: public function serializableRequiresMagicMethods(): bool
272: {
273: return $this->versionId >= 80100;
274: }
275:
276: public function arrayFunctionsReturnNullWithNonArray(): bool
277: {
278: return $this->versionId < 80000;
279: }
280:
281: // see https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.core.string-number-comparision
282: public function castsNumbersToStringsOnLooseComparison(): bool
283: {
284: return $this->versionId >= 80000;
285: }
286:
287: public function supportsCallableInstanceMethods(): bool
288: {
289: return $this->versionId < 80000;
290: }
291:
292: public function supportsJsonValidate(): bool
293: {
294: return $this->versionId >= 80300;
295: }
296:
297: public function supportsConstantsInTraits(): bool
298: {
299: return $this->versionId >= 80200;
300: }
301:
302: public function supportsNativeTypesInClassConstants(): bool
303: {
304: return $this->versionId >= 80300;
305: }
306:
307: public function supportsAbstractTraitMethods(): bool
308: {
309: return $this->versionId >= 80000;
310: }
311:
312: public function supportsOverrideAttribute(): bool
313: {
314: return $this->versionId >= 80300;
315: }
316:
317: public function supportsDynamicClassConstantFetch(): bool
318: {
319: return $this->versionId >= 80300;
320: }
321:
322: public function supportsReadOnlyClasses(): bool
323: {
324: return $this->versionId >= 80200;
325: }
326:
327: public function supportsReadOnlyAnonymousClasses(): bool
328: {
329: return $this->versionId >= 80300;
330: }
331:
332: public function supportsNeverReturnTypeInArrowFunction(): bool
333: {
334: return $this->versionId >= 80200;
335: }
336:
337: public function supportsPregUnmatchedAsNull(): bool
338: {
339: // while PREG_UNMATCHED_AS_NULL is defined in php-src since 7.2.x it starts working as expected with 7.4.x
340: // https://3v4l.org/v3HE4
341: return $this->versionId >= 70400;
342: }
343:
344: public function supportsPregCaptureOnlyNamedGroups(): bool
345: {
346: // https://php.watch/versions/8.2/preg-n-no-capture-modifier
347: return $this->versionId >= 80200;
348: }
349:
350: public function hasDateTimeExceptions(): bool
351: {
352: return $this->versionId >= 80300;
353: }
354:
355: public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool
356: {
357: // Before PHP 8.0, when setting CURLOPT_URL, an unparsable URL or a file:// scheme would fail if open_basedir is used
358: // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L139-L158
359: // https://github.com/php/php-src/blob/php-8.0.0/ext/curl/interface.c#L128-L130
360: return $this->versionId < 80000;
361: }
362:
363: public function highlightStringDoesNotReturnFalse(): bool
364: {
365: return $this->versionId >= 80400;
366: }
367:
368: public function deprecatesImplicitlyNullableParameterTypes(): bool
369: {
370: return $this->versionId >= 80400;
371: }
372:
373: public function substrReturnFalseInsteadOfEmptyString(): bool
374: {
375: return $this->versionId < 80000;
376: }
377:
378: }
379: