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