1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Php;
4:
5: use PHPStan\DependencyInjection\AutowiredService;
6: use function floor;
7:
8: /**
9: * Represents a specific PHP version for version-dependent analysis behavior.
10: *
11: * The version is stored as PHP_VERSION_ID format (e.g. 80100 for PHP 8.1.0).
12: * Extension developers can access it by injecting PhpVersion via constructor injection.
13: *
14: * @api
15: */
16: #[AutowiredService(factory: '@PHPStan\Php\PhpVersionFactory::create')]
17: final class PhpVersion
18: {
19:
20: public const SOURCE_RUNTIME = 1;
21: public const SOURCE_CONFIG = 2;
22: public const SOURCE_COMPOSER_PLATFORM_PHP = 3;
23: public const SOURCE_UNKNOWN = 4;
24:
25: /**
26: * @api
27: * @param self::SOURCE_* $source
28: */
29: public function __construct(private int $versionId, private int $source = self::SOURCE_UNKNOWN)
30: {
31: }
32:
33: /**
34: * @return self::SOURCE_*
35: */
36: public function getSource(): int
37: {
38: return $this->source;
39: }
40:
41: public function getSourceLabel(): string
42: {
43: switch ($this->source) {
44: case self::SOURCE_RUNTIME:
45: return 'runtime';
46: case self::SOURCE_CONFIG:
47: return 'config';
48: case self::SOURCE_COMPOSER_PLATFORM_PHP:
49: return 'config.platform.php in composer.json';
50: }
51:
52: return 'unknown';
53: }
54:
55: public function getVersionId(): int
56: {
57: return $this->versionId;
58: }
59:
60: public function getMajorVersionId(): int
61: {
62: return (int) floor($this->versionId / 10000);
63: }
64:
65: public function getMinorVersionId(): int
66: {
67: return (int) floor(($this->versionId % 10000) / 100);
68: }
69:
70: public function getPatchVersionId(): int
71: {
72: return (int) floor($this->versionId % 100);
73: }
74:
75: public function getVersionString(): string
76: {
77: $first = $this->getMajorVersionId();
78: $second = $this->getMinorVersionId();
79: $third = $this->getPatchVersionId();
80:
81: return $first . '.' . $second . ($third !== 0 ? '.' . $third : '');
82: }
83:
84: public function supportsNullCoalesceAssign(): bool
85: {
86: return $this->versionId >= 70400;
87: }
88:
89: public function supportsParameterContravariance(): bool
90: {
91: return $this->versionId >= 70400;
92: }
93:
94: public function supportsReturnCovariance(): bool
95: {
96: return $this->versionId >= 70400;
97: }
98:
99: public function supportsNoncapturingCatches(): bool
100: {
101: return $this->versionId >= 80000;
102: }
103:
104: public function supportsNativeUnionTypes(): bool
105: {
106: return $this->versionId >= 80000;
107: }
108:
109: public function deprecatesRequiredParameterAfterOptional(): bool
110: {
111: return $this->versionId >= 80000;
112: }
113:
114: public function deprecatesRequiredParameterAfterOptionalNullableAndDefaultNull(): bool
115: {
116: return $this->versionId >= 80100;
117: }
118:
119: public function deprecatesRequiredParameterAfterOptionalUnionOrMixed(): bool
120: {
121: return $this->versionId >= 80300;
122: }
123:
124: public function supportsLessOverridenParametersWithVariadic(): bool
125: {
126: return $this->versionId >= 80000;
127: }
128:
129: public function supportsThrowExpression(): bool
130: {
131: return $this->versionId >= 80000;
132: }
133:
134: public function supportsClassConstantOnExpression(): bool
135: {
136: return $this->versionId >= 80000;
137: }
138:
139: public function supportsLegacyConstructor(): bool
140: {
141: return $this->versionId < 80000;
142: }
143:
144: public function supportsPromotedProperties(): bool
145: {
146: return $this->versionId >= 80000;
147: }
148:
149: public function supportsParameterTypeWidening(): bool
150: {
151: return $this->versionId >= 70200;
152: }
153:
154: public function supportsUnsetCast(): bool
155: {
156: return $this->versionId < 80000;
157: }
158:
159: public function supportsNamedArguments(): bool
160: {
161: return $this->versionId >= 80000;
162: }
163:
164: public function throwsTypeErrorForInternalFunctions(): bool
165: {
166: return $this->versionId >= 80000;
167: }
168:
169: public function throwsValueErrorForInternalFunctions(): bool
170: {
171: return $this->versionId >= 80000;
172: }
173:
174: public function supportsHhPrintfSpecifier(): bool
175: {
176: return $this->versionId >= 80000;
177: }
178:
179: public function isEmptyStringValidAliasForNoneInMbSubstituteCharacter(): bool
180: {
181: return $this->versionId < 80000;
182: }
183:
184: public function supportsAllUnicodeScalarCodePointsInMbSubstituteCharacter(): bool
185: {
186: return $this->versionId >= 70200;
187: }
188:
189: public function isNumericStringValidArgInMbSubstituteCharacter(): bool
190: {
191: return $this->versionId < 80000;
192: }
193:
194: public function isNullValidArgInMbSubstituteCharacter(): bool
195: {
196: return $this->versionId >= 80000;
197: }
198:
199: public function isInterfaceConstantImplicitlyFinal(): bool
200: {
201: return $this->versionId < 80100;
202: }
203:
204: public function supportsFinalConstants(): bool
205: {
206: return $this->versionId >= 80100;
207: }
208:
209: public function supportsReadOnlyProperties(): bool
210: {
211: return $this->versionId >= 80100;
212: }
213:
214: public function supportsEnums(): bool
215: {
216: return $this->versionId >= 80100;
217: }
218:
219: public function supportsPureIntersectionTypes(): bool
220: {
221: return $this->versionId >= 80100;
222: }
223:
224: public function supportsCaseInsensitiveConstantNames(): bool
225: {
226: return $this->versionId < 80000;
227: }
228:
229: public function hasStricterRoundFunctions(): bool
230: {
231: return $this->versionId >= 80000;
232: }
233:
234: public function hasTentativeReturnTypes(): bool
235: {
236: return $this->versionId >= 80100;
237: }
238:
239: public function supportsFirstClassCallables(): bool
240: {
241: return $this->versionId >= 80100;
242: }
243:
244: public function supportsArrayUnpackingWithStringKeys(): bool
245: {
246: return $this->versionId >= 80100;
247: }
248:
249: public function throwsOnInvalidMbStringEncoding(): bool
250: {
251: return $this->versionId >= 80000;
252: }
253:
254: public function supportsPassNoneEncodings(): bool
255: {
256: return $this->versionId < 70300;
257: }
258:
259: public function producesWarningForFinalPrivateMethods(): bool
260: {
261: return $this->versionId >= 80000;
262: }
263:
264: public function deprecatesDynamicProperties(): bool
265: {
266: return $this->versionId >= 80200;
267: }
268:
269: public function strSplitReturnsEmptyArray(): bool
270: {
271: return $this->versionId >= 80200;
272: }
273:
274: public function supportsDisjunctiveNormalForm(): bool
275: {
276: return $this->versionId >= 80200;
277: }
278:
279: public function serializableRequiresMagicMethods(): bool
280: {
281: return $this->versionId >= 80100;
282: }
283:
284: public function arrayFunctionsReturnNullWithNonArray(): bool
285: {
286: return $this->versionId < 80000;
287: }
288:
289: // see https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.core.string-number-comparision
290: public function castsNumbersToStringsOnLooseComparison(): bool
291: {
292: return $this->versionId >= 80000;
293: }
294:
295: public function nonNumericStringAndIntegerIsFalseOnLooseComparison(): bool
296: {
297: return $this->versionId >= 80000;
298: }
299:
300: public function supportsCallableInstanceMethods(): bool
301: {
302: return $this->versionId < 80000;
303: }
304:
305: public function supportsJsonValidate(): bool
306: {
307: return $this->versionId >= 80300;
308: }
309:
310: public function supportsConstantsInTraits(): bool
311: {
312: return $this->versionId >= 80200;
313: }
314:
315: public function supportsNativeTypesInClassConstants(): bool
316: {
317: return $this->versionId >= 80300;
318: }
319:
320: public function supportsAbstractTraitMethods(): bool
321: {
322: return $this->versionId >= 80000;
323: }
324:
325: public function supportsOverrideAttribute(): bool
326: {
327: return $this->versionId >= 80300;
328: }
329:
330: public function supportsDynamicClassConstantFetch(): bool
331: {
332: return $this->versionId >= 80300;
333: }
334:
335: public function supportsReadOnlyClasses(): bool
336: {
337: return $this->versionId >= 80200;
338: }
339:
340: public function supportsReadOnlyAnonymousClasses(): bool
341: {
342: return $this->versionId >= 80300;
343: }
344:
345: public function supportsNeverReturnTypeInArrowFunction(): bool
346: {
347: return $this->versionId >= 80200;
348: }
349:
350: public function supportsPregUnmatchedAsNull(): bool
351: {
352: // while PREG_UNMATCHED_AS_NULL is defined in php-src since 7.2.x it starts working as expected with 7.4.x
353: // https://3v4l.org/v3HE4
354: return $this->versionId >= 70400;
355: }
356:
357: public function supportsPregCaptureOnlyNamedGroups(): bool
358: {
359: // https://php.watch/versions/8.2/preg-n-no-capture-modifier
360: return $this->versionId >= 80200;
361: }
362:
363: public function supportsPropertyHooks(): bool
364: {
365: return $this->versionId >= 80400;
366: }
367:
368: public function supportsFinalProperties(): bool
369: {
370: return $this->versionId >= 80400;
371: }
372:
373: public function supportsAsymmetricVisibility(): bool
374: {
375: return $this->versionId >= 80400;
376: }
377:
378: public function supportsAsymmetricVisibilityForStaticProperties(): bool
379: {
380: return $this->versionId >= 80500;
381: }
382:
383: public function supportsLazyObjects(): bool
384: {
385: return $this->versionId >= 80400;
386: }
387:
388: public function hasDateTimeExceptions(): bool
389: {
390: return $this->versionId >= 80300;
391: }
392:
393: public function isCurloptUrlCheckingFileSchemeWithOpenBasedir(): bool
394: {
395: // Before PHP 8.0, when setting CURLOPT_URL, an unparsable URL or a file:// scheme would fail if open_basedir is used
396: // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L139-L158
397: // https://github.com/php/php-src/blob/php-8.0.0/ext/curl/interface.c#L128-L130
398: return $this->versionId < 80000;
399: }
400:
401: /**
402: * whether curl handles are represented as 'resource' or CurlShareHandle
403: */
404: public function supportsCurlShareHandle(): bool
405: {
406: return $this->versionId >= 80000;
407: }
408:
409: public function supportsCurlSharePersistentHandle(): bool
410: {
411: return $this->versionId >= 80500;
412: }
413:
414: public function highlightStringDoesNotReturnFalse(): bool
415: {
416: return $this->versionId >= 80400;
417: }
418:
419: public function deprecatesImplicitlyNullableParameterTypes(): bool
420: {
421: return $this->versionId >= 80400;
422: }
423:
424: public function substrReturnFalseInsteadOfEmptyString(): bool
425: {
426: return $this->versionId < 80000;
427: }
428:
429: public function supportsBcMathNumberOperatorOverloading(): bool
430: {
431: return $this->versionId >= 80400;
432: }
433:
434: public function hasPDOSubclasses(): bool
435: {
436: return $this->versionId >= 80400;
437: }
438:
439: public function deprecatesImplicitlyFloatConversionToInt(): bool
440: {
441: return $this->versionId >= 80100;
442: }
443:
444: public function deprecatesNullArrayOffset(): bool
445: {
446: return $this->versionId >= 80500;
447: }
448:
449: public function supportsFinalPromotedProperties(): bool
450: {
451: return $this->versionId >= 80500;
452: }
453:
454: public function supportsVoidCast(): bool
455: {
456: return $this->versionId >= 80500;
457: }
458:
459: public function supportsNoDiscardAttribute(): bool
460: {
461: return $this->versionId >= 80500;
462: }
463:
464: public function deprecatesNonStandardCasts(): bool
465: {
466: return $this->versionId >= 80500;
467: }
468:
469: public function deprecatesBacktickOperator(): bool
470: {
471: return $this->versionId >= 80500;
472: }
473:
474: public function supportsAttributesOnGlobalConstants(): bool
475: {
476: return $this->versionId >= 80500;
477: }
478:
479: public function supportsDeprecatedTraits(): bool
480: {
481: return $this->versionId >= 80500;
482: }
483:
484: public function supportsOverrideAttributeOnProperty(): bool
485: {
486: return $this->versionId >= 80500;
487: }
488:
489: public function deprecatesDecOnNonNumericString(): bool
490: {
491: return $this->versionId >= 80300;
492: }
493:
494: public function deprecatesIncOnNonNumericString(): bool
495: {
496: return $this->versionId >= 80500;
497: }
498:
499: public function supportsObjectsInArraySumProduct(): bool
500: {
501: return $this->versionId >= 80300;
502: }
503:
504: }
505: