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